char *p = NULL;
...
if (p && *p != ' ') { ... }
If you write bool p_non_null = (p != NULL);
bool char_non_space = (*p != ' ');
if p_non_null && char_ non_space { ... }
Oops. char *p = NULL;
...
if (p && *p != ' ') { ... }
If you write bool p_non_null = (p != NULL);
bool char_non_space = (*p != ' ');
if p_non_null && char_ non_space { ... }
Oops. alias not_null = (bool)p;
alias not_space = (*p != ' ');
if (not_null && not_space) {
This may bite you in another way, but living in a world where everything gets evaluated asap-y isn’t syntactically easy either.Would be also nice to have this:
alias __cache foo = <heavy(expr())>;
if (foo && foo.length && foo[0].x) {
`foo` only evaluates once. auto not_null = [&]{ return (bool)p; };
auto not_space = [&]{ return (*p != ' '); };
if (not_null() && not_space()) {
It's not quite as ergonomic but it works.As an aside. I was curious if the compiler will optimize in the short circuit even if you extract the eagerly evaluated named booleans. However I was pretty sure the rules of c++ are against you and tests show this to be the case
But if you simulate the alias keyword with lambdas then the result after optimization are the same
It will optimize, but not necessarily what you wanted... Because null dereference is UB (with default compiler flags), it's allowed to elide null check.
#DEFINE NOT_NULL(x) (bool)x
#DEFINE NOT_SPACE(x) ( ' ' != *x )
// ...
if ( NOT_NULL(v) && NOT_SPACE(v) ) { ... }The reason is mainly the many language construct of C++ cover most traditional use cases of macros when programming C while at the same time being much less error prone than macros.
A sibling show how one easily can achieve the very same thing using short lambdas instead. That variant is also bound to the scope one is currently in, and in order to have that safety with a macro one would need to undefine the macros afterwards. One would also need to check that they are not yet defined beforehand, and if so restore the previous macro afterwards in order to not break completely unrelated code in case of macro name clashes.
Define your booleans as functions of no args. A sample in Python:
var = 'abc'
var_contains_a = lambda: 'a' in var
var_shorter_than_5 = lambda: len(var) < 5
if var_contains_a() and var_shorter_than_5():
print(var)Taking the last top-level statement, the `if`:
if var_contains_a() and var_shorter_than_5: #note the lack of parens for the second
print(var)
This will print a string of any length that contains 'a' (or any other collection that implements `in` and does, in fact, contain an element, 'a'). The second function is passed directly as a boolean expression, and being a function, it resolves to true.I'm curious as to if it's feasible to syncretise these two aspects of variable evaluation into a shared feature that makes async and sync code feel alike.
That's how Haskell works. Everything is lazily evaluated, and in the case that evaluation is needed now, the "seq" function obtains it (https://wiki.haskell.org/Seq).
bool char_non_space = p_non_null && (*p != ' ');But I don't understand your "inefficient" point. At the end of the day, everything gets turned into SSA. So expression-per-line and nested-expressions generates the same code, right?
bool dirty = ...;
bool success = doExpensiveCalculation(...);
vs if (dirty && doExpensiveCalculation)
{ if (p_non_null = (p != NULL)) && (char_non_space = (*p != ' ')) { ... }
But where the IDE only shows the LHS of the expression as: if p_non_null && char_ non_space { ... }
But also where the IDE makes it easier to write the earlier full expression above.Also, the initial code:
bool p_non_null = (p != NULL);
bool char_non_space = (*p != ' ');
if p_non_null && char_ non_space { ... }
This could simply be analyzed by the compiler to not store the named boolean variables in the first place, and to evaluate them inline in the if-condition so as to allow for the short-circuiting to occur fine.