Basic short-circuit evaluation question

Hi all,

So I have an if clause with short-circuit operator &&. I understand that if I have something like:

if (bool1 && bool2) { do.stuff(); },

and bool1 evaluates to false, then bool2 is never evaluated and the statement returns false (so do.stuff() is never executed).

But, if I have something like this:

if (bool1 && Foo.execute() { do.stuff(); },

where Foo.execute() attempts to execute some code, and returns a bool, would I be correct in assuming that, if bool1 is false, Foo.execute() is never called?

Thanks,

That is correct. If the statement can already be determined by the first operand, the rest of the condition is not evaluated.

See Logical operators - cppreference.com

  • 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 operands
2 Likes

If you want the 2nd condition to always evaluate even if first one is false, just use & not &&

2 Likes

AFAIK, the single ā€˜&ā€™ is a bitwise operator, it is for operating on bits (on integer values) and, while it may show the behavior you describe, it REALLY feels like thatā€™s more a side effect than an intended feature. And if thatā€™s the case, I believe relying on that should be avoided.

Or is there some special case for bools that Iā€™m not aware of?

2 Likes

A definite code smell there. Another programmer might come along and assume it was a typo and ā€œfixā€ it.

Much clearer would be:

auto conditionA = someInt == 3;
auto conditionB = mustEvaluateThis();
if(conditionA && conditionB) {
    // do something
}
3 Likes

@yfede when you are using & you are indeed using bitwise operators to operate with 2 booleans rather than using ā€œlogic operations/truth tablesā€. The booleans are converted to integers 0 (if false) and 1 (if true) and applies a bitwise operation:

  • X & 1 = 1
  • X & 0 = 0
    (No matter the value of X, but in our case X will be only 0 or 1 as itā€™s just a boolean)

On the other hand the always used && (and) as I understsand doesnā€™t need to evaluate the 2nd term if the first one is false as itā€™s making a logic operation, while in the & case as you are doing ā€œnormal operationsā€ it evaluates every term.

The thing is that if you want to always evaluate one condition you can just put it first, like:

if (mustEvaluate && conditionB) ...

But if for some very specific reason you must evaluate both conditions (i.e both are functions that not only return a boolean but also do other needed logic) then you can use &. I get many will dislike using it and prefer doing all this stuff in another way as itā€™s less readable, but itā€™s just an option.

But I bet someone else can shed some light into the why && vs & work different

TLDR: && = and, & = bitand

Is this the case or are you assuming that?

What if a compiler implements a bool version that behaves correctly like &&?

I wouldnā€™t be surprised, if that depends on the platform and therefore I wouldnā€™t tolerate that in my code.

Itā€™s standard. ā€œArithmetic operators do not accept types smaller than int as argumentsā€, if no operand is floating-point then ā€œboth operands are integersā€, and ā€œif the source type is bool, the value false is converted to zero and the value true is converted to the value one of the destination typeā€. Personally, I donā€™t find precomputing each part any clearer than commenting ā€œ& intendedā€. Shortcircuit evaluation is not syntactically self-evident, and precomputing makes the difference seem greater than it is.

3 Likes

This ā€œproblemā€ only arises when youā€™re calling at least two functions which have side effects, where those side-effects are essential to the correct running of the program, and where you also need the return values from both those calls.

Nowā€¦ if both those functions modify the programā€™s state, then you also need to worry about the order in which they get called. If you use & then the compiler could (and will) call them in an arbitrary order. That normally manifests when you compile your app with gcc you get weird behaviour that takes ages to track down.

Whenever you call any non-pure function which modifies state, a good rule of thumb is that it should get a statement to itself.

So IMHO the trick with & is bad in quite a few ways, not least because having to add a comment to stop people mis-understanding a deliberately weird syntax choice is pretty nasty.

Just making the calls in the order you need them to happen, and assigning the results to bool variables with useful names, and then checking those variables later in the if statement is almost always the best style to use.

4 Likes

Well, it may happen that the order doesnā€™t matter. Anyway, Iā€™ve used this kind of thing temporarily, but always end up redesigning so that itā€™s not needed anymore. If both side effects need to be performed unconditionally, but both returns condition something else, most often the side effects and the returns are poorly conflated, and thereā€™s something not quite logical about the structure.

1 Like

What about

bool anyChange = false;
for (auto& x : stuff)
    anyChange |= x.update();
if (anyChange)
    recomputeThings();

The order is preserved, even though it probably doesnā€™t matter.

2 Likes

Not a big fan of that, though sometimes if thereā€™s a loop then itā€™s the simplest pattern.

But you should at least avoid the bitwise operator, e.g.

bool anyChange = false;

for (auto& x : stuff)
    anyChange = x.update() || anyChange;

if (anyChange)
    recomputeThings();

Though TBH I think Iā€™d tend to write it like this for clarity:

bool anyChange = false;

for (auto& x : stuff)
    if (x.update())
        anyChange = true;

if (anyChange)
    recomputeThings();
4 Likes

Does this also go for using function returns as arguments in functions?

What I mean is something like

maybeThisFunctionUpdatesBufferSize(buffer.getNumSamples(), buffer.getNumChannels());

obviously this is a simple example and we can be certain that getNumSamples() and getNumChannels() are just getters that donā€™t alter program state. But is doing this sort of thing considered bad practice? If those were functions that updated program state, then would it be a super no-no?

Would the preferred version be

auto numSamples = buffer.getNumSamples();
auto numChannels = buffer.getNumChannels();
maybeThisFunctionUpdatesBufferSize(numSamples, numChannels);

Thanks :slightly_smiling_face:

They are equivalent in practice, because also in the one-line version, all the expressions that appear as arguments of the function maybeThisFunctionUpdatesBufferSize are guaranteed to be evaluated (but not in a specific order! I also vaguely remember that the order in which they are evaluated is guaranteed, (from last argument to first?), but for code clarity I tend not to depend on that, and anyway that has nothing to do with short-circuit evaluation).

Short-circuit only comes into play when you have boolean operators, like &&, ||.

1 Like

Thatā€™s not true. The order of argument evaluation is unspecified, so code that depends on evaluation happening in a specific order is not portable.

In a function call, value computations and side effects of the initialization of every parameter are indeterminately sequenced with respect to value computations and side effects of any other parameter.

2 Likes

Ah thanks, good to know, I seem to have formed that impression just by watching the order they were called in various occasions while debugging. Iā€™m editing my post above to avoid confusion to future readers

1 Like

FWIW in my experience, GCC and Clang tend to evaluate in the opposite order, which is actually quite handy for catching mistakes.

Weā€™ve had a couple of head-scratchers in soul where some code that worked perfectly well on our local machines (Clang) suddenly started failing CI in the linux (GCC) builds, because weā€™d got some functions with side-effects in the arguments. In a big, complex codebase (like the soul compiler!), it can end up being incredibly hard to track down exactly where the mistake is, because the function call itself doesnā€™t look wrong, and the side-effects could be buried deeply.

Itā€™d be nice if the compiler could detect and warn when you have side-effects in multiple argument expressions. In practice thereā€™d probably be a lot of false alarms, but I think thatā€™d make a great option for projects with stricter warning levels.

4 Likes