Named Booleans prevent C++ bugs and save you time
raymii.org
raymii.org
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. bool char_non_space = p_non_null && (*p != ' '); 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.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).
#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. 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.
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. if (someLongNamedVarIsNotUnknown && someLongNamedMapCountIsZero)
return false;
else
return true;
This is not the same, but I'd suspect there might be a bug in the code above if (someLongNamedVarIsUnknown) {
return false; // Can't do much
}
if (someLongNamedMapCountIsZero) {
return false; // Nothing to do
}
// Do stuff
return true
The named booleans suggested here help adding clarity, but more often than not they'll end up being single-use (const) variables that just paraphrase their definitions. In these cases I'd rather avoid introducing a variable, and rely in comments if I really need to explain something. const value = if (a) {
return true
} else {
return switch (b) {
case 1: return false
default: return undefined
}
}In a typical programming language we can write something like x = 5; assigning 5 to the variable x. That 5 there is an expression, we can write x = (3 * 2) - 1; with the same effect, that's a more complicated expression. We can call a function here: x = f(3); or if our language has methods we can even call those: x = some.function(parameters);
In Rust I can write x = if it.should_be_nine() { 9 } else { 5 }; // because if is an expression
Since Rust's loops are also expressions, x = loop { let stuff = get_stuff(); if stuff.is_good() { break stuff } }; // ... once the stuff is good, break out of the loop and we'll assign that stuff to x
My informal understanding of a predicate may need refining.
It might be you've just always used languages where you can do this, or that you assumed you can but actually you never tried in a language you use.
In C for example we can't write this:
int x = if (1 == 2) { 3 } else { 4 }; // Not valid C syntax
C provides a special operator, the only one in the language which has three operands and thus it's often just called "the ternary operator" although other ternary operators can technically exist, to achieve the same thing: int x = (1 == 2) ? 3 : 4; // Valid C syntax using ternary operator
But, how about this? int x = switch 3 { case 1: 4; case 2: 8; default: 0; }; // Can't do this either
There's no way to express that in C or C++ today. The closest is the "Immediately Invoked Lambda" from C++ which is illustrated nearby in this thread. Barry Revzin apparently proposed a way to do this in C++ 26 because C++ wants to have pattern matching and pattern matching kinda sucks without this feature.https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
const value = [&]() {
if (a) {
return true;
}
return switch (b) {
case 1: return false;
default: return undefinied;
}
}();Still the syntax is a bit confusing. In ML languages (f# and also rust are somehow descendants of ML) you can just use if .. else if .. else expressions. They work like ternaries! But easier readable. Nested ternaries are useful, but get really hard to read very fast. (x = condition1 ? A : condition2 ? B : C;)
LLVM and GCC can at least.
const value = (()=>{
…
})() let value = if a { true } else { match b { 1 => false, _ => undefined } };
And notice that because now we're a function our relationship to the world changed, how does your immediately invoked lambda do this? let value = if a { true } else { match b { 1 => false, 2 => return foo, _ => undefined }};
With expression syntax returning from the function we're in is fine† but in your lambda you can't do that, you need control flow logic outside the lambda to handle it.† You might wonder about the inferred type of this expression since it might return. The type of the return expression in Rust is ! aka Never, which is an empty type. In the same way that N + 0 == N in integer arithmetic, any type plus an empty type is the original type again in type arithmetic, so the type of the expression isn't altered.
value := (if a then True
else (
case b is
when 1 => False,
when others => Undefined
));Interesting. With the same axioms I come to the opposite conclusion. That comments are to be avoided except when strictly necessary, and giving names to concepts via intermediate const variables is one of the best weapons in that fight. Ie. make your code read as close to what the comment would be (but simply via naming) makes it very clear when the names no longer match the behavior, in contrast to comments which have a tendency to bit rot.
That's ok. The worst tradeoff in the "named booleans" pattern proposed in the blog post is that it misses the whole point of chaining predicate evaluations, which is leveraging short-circuiting to prevent further predicates from being evaluated.
Chaining logical comparisons and the way they short-circuit is specially important when a predicate has preconditions and/or is expensive and/or has side-effects.
If your predicates are hard to read you don't fix that mess with "named booleans". You fix them by extracting them to a pure function like
isNotUnknown(_someLongNamedVar) && isNotEmpty(_someLongNamedVar)Says who? IME relying on short circuiting is useful and idiomatic in C and C++ code, e.g.:
if (callback && callback->run()) { ... }
In your opinion would it be better to write: if (callback) {
if (callback->run()) {
..
}
}
The situation is even more awkward if you try to avoid || short-circuiting, since you need to duplicate the body of the conditional.In a case like this, my preference would be to give callback a dummy value and always run it.
I subscribe pretty heavily to the Carmack philosophy of always running the code as similarity as possible and making a decision at the end of a procedure.
I'm curious, what leads you to believe that skipping unnecessary calls to functions with side effects is something to be avoided? Do you believe that unconditionally calling code with side effects specially when you don't have to is something to be desired?
I would almost always rather support default behavior through dummy data rather than null checks. I find that a lot of software bugs come from the unintended consequences of branching. Especially branches which create state that is consumed later.
In fact, I find that most null checks lead to bad software behavior as it often hides bugs. Early outting when null is detected is especially silly to me. The client has just tried to run a procedure which had to be abandoned due to a lack of data and you're just going to quit silently?
If you're using dummy data for default scenarios and asserting not null rather than checking, you will eliminate most logical null checks in your code. Though I do concede that in the case where you must support null checks, such a short circuit as above is acceptable and in fact preferable to me over two if statements.
In the above example, we check that the pointer is non-null before calling it. The "side effect" we are avoiding is a segfault.
In the code that splits the conditional into two, the callback is still run inside a conditional, just by itself.
in mine, yes definitely 100% the second code is more readable than the first
If anything, adding a nesting level for each chained predicate makes code harder to read, and needlessly so.
The issue is not verbosity. It's the needless addition of nesting levels and independent expressions in a non-idiomatic way, which greatly increase the cognitive load of checking a single row of chained predicates. It's bad code.
if(an expression) {
if(another expression) {
}
}
is MUCH lower than if(an expression && another expression) {
}
which always raise the questions of:- did the author think about operator precedence
- did the author want to use && or &
- did the author think about short-circuiting (as evidenced in this very thread, a lot of people do not)
I question the idea that you would ever want the very linchpin of your function hidden away as a second subclause of an if statement. A function that calls callbacks is probably mainly doing that, let it stand proud and clear in the code text.
If your function is calling callbacks left and right “on its way somewhere else”, I think a refactor is in order.
Maybe you will now present some second scenario with something other than callbacks. Maybe in that case you’ll have me convinced it’s an okay exception. For you see, I said only it’s “pretty shaky”, not a mortal sin.
With that said though, I think in 9/10 cases, it’s something you can solve with an early return.
if (!callback)
return;
callback();You've got it all backwards. Short-circuiting is not used to avoid side effects. Short-circuiting is used to avoid needless calls to any predicate, which can have and often do has side-effects, and if it doesn't have today it might have tomorrow.
If you don't call them when you don't have to, your code is in a better shape.
And let's not pretend that unconditionally macode with side effects when you don't have to is a good practice.
The point is not performance. The point is readability, and expressing things clearly and with the appropriate level of detail and leaving out nesting and scope. Safety and performance are important but secondary advantages.
if ((_someLongNamedVar != FooLongNameEnum::Unknown) && (_someLongNamedMap.count (_someLongNamedVar) == 0))
really not too bad? And easily enforcable and automated checkable by your favourite tool..
Named vars for subexpressions often helpful too.. but especially here you loose short-circuiting?
(Not very C++ specific topic btw..?)
if (b1 && (b2 != b3))
rather than if ((b1) && (b1 != b3))99.9% when the condition is "too hard to read" it's because the condition is too hard to read, period, and the correct fix is to restructure the code to have readable branch conditions, not to throw parentheses on there to make a monster expression slightly less confusing.
It's like discussing the chemistry of air fresheners instead of taking the trash out more often.
There are certainly times when I take the name approach nonetheless, but more like a last resort, or when I expect full contact stepthrough with a debugger. If I deem it likely that I will get by with merely brackets and copious amounts of whitespace, that's what I'll do.
deliveryTime = 3;
if (primeMember) {
deliveryTime = 1;
}
you can pass in a `Membership memberType` value that contains the required information, so the above conditional turns into just the assignment deliveryTime = memberType.deliveryTime;
This is more amenable to extension and contraction: you can add or remove different classes of delivery times without changing the logic in which it's used.You can even use private constructors and public static fields to create a list of "constants" containing the known member types, like the `Membership.REGULAR` and `Membership.PRIME`, retaining the property of the code not being able to use an invalid membership type.
This can be used also for more complicated conditionals like the one in the article, by having a factory method that copies the right "constant" composite value based on whatever logic is desired.
On the downside, this breaks single-assignment, binding style variables. That is, now deliveryTime can’t be final/val/const.
I wish I had a better solution. Lisp did this so well with if/cond/etc as expressions.
One example of a language without that implicit conversion is Java. It's fairly annoying to have to put a "!= 0" whenever you want to convert a number to a boolean, but it does avoid a lot of programming mistakes within if conditions.
But it is sad that in $current_year, we're still fighting boring, solved problems.
In this [0] example I shared in other related comments, the static analyzer suppressed warnings in such headers.
> 126 warnings generated.
> Suppressed 124 warnings (124 in non-user code).
2. This would be caught by static analyzer
See Compiler Explorer example here [0]
I'm not saying C++ is a great language, it's not, but if you remove the secu then shoot you in the foot, it's your fault.
-Wall -Werror does not catch the error described in the blog.
It does get caught by a static analyzer though [0].
The software I'm working on is safety-critical. I enabled every relevant checker relevant for certificates and tons of additional checkers related to usability and readability. clang-tidy now takes 3x the time it takes to compile :S I ended up splitting off a few checkers in particular into their own separate job.
Some checkers are also quite broken. bugprone-unchecked-optional-access is very broken. It caused hangs, it caused crashes, it caused out-of-memory issues. Fortunately, our use of std::optional can mostly be refactored away but doing so is a significant change in the codebase.
In practice you tend to see such casts in code where we directly needed the integer. I think Clippy (Rust's linter) proposes them where you've written if some_bool { 1 } else { 0 } because well, that's what the as cast does anyway.
if (cheapCheck() && expensiveCheck()) { ... }
is not equivalent to bool cheap = cheapCheck();
bool expensive = expensiveCheck();
if (cheap && expensive) { ... }
unless we're in a lazy pure functional language.The ability of the compiler to skip the expensive function in both cases are characteristics of a "lazy" language, ie. you can assign an expensive function to a value, but the actual function isn't run until the variable is needed. Languages like C++ can't do this because it would alter the program's behavior if cheapCheck() does return true, because suddenly it would have to call expensiveCheck() after cheapCheck() (rather than before), which is an observably different behavior for the optimization to cause.
> Builtin operators && and || perform short-circuit evaluation (do not evaluate the second operand if the result is known after evaluating the first), but overloaded operators behave like regular function calls and always evaluate both
[1] https://en.cppreference.com/w/cpp/language/operator_logical [2] https://en.wikipedia.org/wiki/Short-circuit_evaluation
If the operator has been overloaded which is easy to write in C++, then too bad - the overload doesn't short circuit.
I think if you can't figure out a way to preserve the short-circuit feature then having a way to overload this operator in your language is stupid. It is, I would say, unsurprising to me that C++ did it anyway.
EtA:: It feels like in say Rust you could pull this off as a trait LogicalAnd implemented on a type Foo, with a method that takes two parameters of type FnOnce() -> Foo , and then the compiler turns (some_complicated_thing && different_expression) where both of the sub-expressions are of type Foo into something like:
{
let a = (|| some_complicated_thing);
let b = (|| different_expression);
LogicalAnd::logical_and(a, b)
}
To deliver the expected short-circuiting behaviour in your implementation of the logical_and method, you just don't execute b unless you're going to care about the result.And in any case the author misidentified the problem and solution. The problem is that C++ coerces bool to int. I'm 99% sure there's a warning for that that you can turn into an error.
I'm just saying that in this particular case, sometimes the compiler will be able to optimise it but you can't rely on it.
bool cheap = cheapCheck();
bool expensive = cheap && expensiveCheck();
if (expensive) { ... } if (
(_someLongNamedVar != FooLongNameEnum::Unknown) &&
(_someLongNamedMap.count(_someLongNamedVar) == 0)
) {
// do something
} if (true
&& condition1
&& condition2
&& condition3
) {
...
}
If you ever need to change the set of conditions, the diff will cleanly add/remove whole lines. if ( condition1
&& condition2
&& condition3
) {
...
}
Obviously the best alternative is to switch to a prefix language and sidestep the issue entirely: (when
(and
(condition1)
(condition2)
(condition3))
...)1. Avoid overly long and complex condition expressions - break them up for readability.
2. If a part of a long expression has obvious meaning, assign it to a temporary (probably saving a comment); if it doesn't, break a line after it. I don't like assigning variables with arbitrary names that simply duplicate the expression calculating them.
3. `true` and `false` are magic numbers. Calling `my_function(my_data, false)` is quite the hazard in my view: It makes the reader rather likely to fail to notice the exact semantics. I would expect, say,
enum : bool { dont_save_copy = false, save_copy_to_file = true };
my_function(myData, dont_save_copy)
4. This is relevant to almost any language! Even functional ones, where you would be encouraged to use "let" or "with" instead of super-complicated unbroken expressions. If you're writing APL though... you need super-memory and a discipline of steel :-) if (_someLongNamedVar != FooLongNameEnum::Unknown && _someLongNamedMap.count(_someLongNamedVar) == 0)
does exactly what it’s supposed to.This is C++.
The most basic thing has been deprecated but is still supported for backwards compatibility.
Boolean is a perfectly cromulent data type that you can return from a function directly.
if (x1 < y1) return true;
if (x2 < y2) return true;
if (x3 < y3) return true;
return false;
I wouldn't want to replace the last two lines with `return (x3 < y3)`, even though that's functionally the same thing.In this contrived example, we could just return a single compound condition, but hopefully it's clear that there are cases where doing so would make more of a mess, too.
bool is_db_open() {
return (db != NULL);
}
bool is_time_to_report() {
if (n_errors >= limit) {
return true;
}
return false;
// a little confusing way
return n_errors >= limit;
}
Also, if there’s a chance of an additional condition, they can be easily be added and documented. if (n_errors >= limit) {
return true; // too many errors
}
if (now() >= deadline) {
return true; // periodic
}
return false;
For the same reason I often do this: if (x == 0) {
return 0; // not `x`the lengths of names of entities, in any programming language, should be related to their scope - small scope, small names. and as limiting scope is what you should be doing all the time, almost all names should be short.
also, unless you really know what you are doing, don't use leading underscores, or underscores in general, in c or c++.
here is something i wrote a while back, which admitedly does not too much address the single underscore issue: https://punchlet.wordpress.com/2009/12/01/letter-the-second/
Typescript is nice for this in that you can use strings but enforce that they're in some set.
ConnectionMode connectionMode;
if (useHttpsCheckbox.selected == ListItem::Selected) {
connectionMode = ConnectionMode::Https;
} else if (useHttpsCheckbox.selected == ListItem::Unselected) {
connectionMode = ConnectionMode::Http;
} else {
shouldNeverHappen();
}
In the real world there are often even longer chains like this - I found that bool-like variables ofter mean slightly different things at different layers of abstraction.And also, you could simplify that by ignoring the `else` entirely, but there are no guarantees that it won't break later. For example another contributor may add ConnectionMode::TryHttpsElseHttp, and compiler won't help us. Languages with pattern matching don't have this particular problem.
This is actually something I think the C++ standard should address soon. There are cases when extern functions which aren't appropriately pure are still "optional." The example par excellence is debug logging, should one choose to not use macros.
I tend not to work that way, but it’s really a matter of personal style. I do it to reduce the verbosity of my code (which tends to be fairly verbose, in other ways –especially comments). I will, occasionally, encounter the bug he mentions, but not particularly frequently.
If this wording means to convey that the complete test in the wrong code version is always giving the inverse of the correct version, then that statement is wrong. The truth tables, using 0 and 1 for false and true ("false" and "true" look too messy here):
The buggy code:
0 1+ <- second condition
0 1 1
1 1 0
^- first condition
The fixed code: 0 1+ <- second condition
0 0 0
1 1 0
^- first condition
i.e. the test as a whole is not inverted if the first subcondition is true.As has been pointed out, there are cases where it's not efficient or correct, but in general it's very useful. (Been coding since 1965, not always C++ obviously).
A very confusing article.
Why are we even still talking about C++?
There is a lot of code out there already written in C++ and a lot of people working on that code. Not to mention an active standards body.
Expect to be hearing about C++ for a long time to come
bool usersHairColorMatchesThisMonthsAdsColour = _user.hair == HairColour::Red;
bool userFeetSizeFitsInShoe = _user.feetSize <= _requestedShoe.size;
bool shoePriceFitsInUserBudget = _requestedShoe.price <= BudgetHelpers::Calculator(_user);
bool shoeIsProbablyOkayForUser = usersHairColorMatchesThisMonthsAdsColour && userFeetSizeFitsInShoe && shoePriceFitsInUserBudget;
if(shoeIsProbablyOkayForUser)
That they compare, with the "uglier": // this months ad campaign color is red
if(_user.hair == HairColor::Red && _user.feetSize <= _requestedShoe.size && _requestedShoe.price <= BudgetHelpers::Calculator(_user))
I very much prefer the latter, I'm sorry (except it needs some new lines, of course). It performs better (that `BudgetHelper::Calculator` doesn't look very cheap ;) but crucially it is, to me personally, more "readable". First, I find a comment easier to read than a variable name, particularly when you use the not so readable dromedaryCaseToCrambleWordsInLongSentences. Thus, a variable adds no value when it's only used once (arguably, it adds more noise, since one has to go twice through the same text). And it is often debatable how a programmers poor translation of code into English adds any value: in fact, I find `_user.feetSize <= _requestedShoe.size` to be way more succint, precise and easier to parse than `userFeetSizeFitsInShoe`.We really need to get over this school of thought that "the more wordy your variables the more readable" because it is simply not true. You don't have to go all the other way to the other end and write code using greek letters like in math. Meet somewhere in the middle, be aware of context and use the tools of the language (e.g. lexical scoping) to your power.
Unlike many of these bloggers want us to believe, you can't just boil "readability" down to some simple universal rules, and only experience makes the master. Taste, preference, purpose and context take a big part. As such, I find it useless to talk about "readable" code: readable for who? A law text, a maths paper, a school maths book, a user manual, a tutorial, a novel, a HN comment... they may all be written in English and with great skill in consideration for their "readability" by their target audiences, yet manifest completely different styles even when discussing the same topics.
For me, I like my code like I often enjoy most good writting: succint and precise. Wordy variables that add no value or abstraction are of little use to me.
hair_col_matches && size_fits && in_budget
is less than half the length and imho more readable because it doesn't turn each variable intoNeedlesslyWordyCamelCaseSentences.> you can't just boil "readability" down to some simple universal rules
Yes, in the end blindly following rules doesn't turn out as well as understanding why those best practices exist in the first place.