if statement - short circuit evaluation vs readability

C++If StatementShort CircuitingSide Effects

C++ Problem Overview


Sometimes, an if statement can be rather complicated or long, so for the sake of readability it is better to extract complicated calls before the if.

e.g. this:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
	// do stuff
}

into this

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
	//do stuff
}

(provided example is not that bad, it's just for illustration... imagine other calls with multiple arguments, etc.)

But with this extraction I lost the short circuit evaluation (SCE).

  1. Do I really lose SCE every time? Is there some scenario where the compiler is allowed to "optimize it" and still provide SCE?
  2. Are there ways of keeping the improved readability of the second snippet without losing SCE?

C++ Solutions


Solution 1 - C++

One natural solution would look like this:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

This has the benefits of being easy to understand, being applicable to all cases and having short circuit behaviour.


This was my initial solution: A good pattern in method calls and for-loop bodies is the following:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

One gets the same nice performance benefits of shortcircuit evaluation, but the code looks more readable.

Solution 2 - C++

I tend to break down conditions onto multiple lines, i.e.:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Even when dealing with multiple operators (&&) you just need to advance indention with each pair of brackets. SCE still kicks in - no need to use variables. Writing code this way made it much more readible to me for years already. More complex example:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {

Solution 3 - C++

If you have long chains of conditions and what to keep some of the short-circuiting, then you could use temporary variables to combine multiple conditions. Taking your example it would be possible to do e.g.

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

If you have a C++11 capable compiler you could use lambda expressions to combine expressions into functions, similar to the above:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }

Solution 4 - C++

  1. Yes, you no longer have SCE. Otherwise, you would have that

    bool b1 = SomeComplicatedFunctionCall(); bool b2 = OtherComplicatedFunctionCall();

works one way or the other depending if there is an if statement later. Way too complex.

  1. This is opinion based, but for reasonably complex expressions you can do:

    if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall()) {

If it ways too complex, the obvious solution is to create a function that evaluates the expression and call it.

Solution 5 - C++

You can also use:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

and SCE will work.

But it's not much more readable than for example:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

Solution 6 - C++

> 1) Do I really lose SCE every time? Is compiler is some scenario allowed to "optimize it" and still provide SCE?

I don't think such optimization is allowed; especially OtherComplicatedFunctionCall() might have some side effects.

> 2) What is the best practice in such situation? Is it only possibility (when I want SCE) to have all I need directly inside if and "just format it to be as readable as possible" ?

I prefer to refactor it into one function or one variable with a descriptive name; which will preserve both short circuit evaluation and readability:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

And as we implement getSomeResult() based on SomeComplicatedFunctionCall() and OtherComplicatedFunctionCall(), we could decompose them recursively if they're still complicated.

Solution 7 - C++

> 1) Do I really lose SCE every time? Is compiler is some scenario > allowed to "optimize it" and still provide SCE?

No you don't, but it's applied differently:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Here, the compiler won't even run OtherComplicatedFunctionCall() if SomeComplicatedFunctionCall() returns true.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Here, both functions will run because they have to be stored into b1 and b2. Ff b1 == true then b2 won't be evaluated (SCE). But OtherComplicatedFunctionCall() has been run already.

If b2 is used nowhere else the compiler might be smart enough to inline the function call inside the if if the function has no observable side-effects.

> 2) What is the best practice in such situation? Is it only possibility > (when I want SCE) to have all I need directly inside if and "just > format it to be as readable as possible" ?

That depends. Do you need OtherComplicatedFunctionCall() to run because of side-effects or the performance hit of the function is minimal then you should use the second approach for readability. Otherwise, stick to SCE through the first approach.

Solution 8 - C++

Another possibility that short circuits and has the conditions in one place:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

You could put the loop into a function and let the function accept a list of conditions and output a boolean value.

Solution 9 - C++

Very strange: you are talking about readability when nobody mentions the usage of comment within the code:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

In top of that, I always preceed my functions with some comments, about the function itself, about its input and output, and sometimes I put an example, as you can see here:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Obviously the formatting to use for your comments may depend on your development environment (Visual studio, JavaDoc under Eclipse, ...)

As far as SCE is concerned, I assume by this you mean the following:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}

Solution 10 - C++

Readability is necessary if you work in a company and your code will be read by someone else. If you write a program for yourself, it is up to you if you want to sacrifice performance for the sake of comprehensible code.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrelaxxxView Question on Stackoverflow
Solution 1 - C++Horia ComanView Answer on Stackoverflow
Solution 2 - C++AmigoJackView Answer on Stackoverflow
Solution 3 - C++Some programmer dudeView Answer on Stackoverflow
Solution 4 - C++SJuan76View Answer on Stackoverflow
Solution 5 - C++KIIVView Answer on Stackoverflow
Solution 6 - C++songyuanyaoView Answer on Stackoverflow
Solution 7 - C++Hatted RoosterView Answer on Stackoverflow
Solution 8 - C++levilimeView Answer on Stackoverflow
Solution 9 - C++DominiqueView Answer on Stackoverflow
Solution 10 - C++br0llyView Answer on Stackoverflow