3

Consider a function like the below:

unsigned int fact(unsigned int i) {
    if (i <= 1) { return 1; }
    return i * fact(i-1);
}

If I were to instantiate a new variable unsigned int f such that f = 0 * fact(5), why does it not "short circuit"?

unsigned int fact(unsigned int i) {
    std::cout << "a";
    if (i <= 1) { return 1; }
    return i * fact(i-1);
}

int main() {
    unsigned int f = 0 * fact(5);
}

The output here is aaaaa. If f can only ever be zero, why would it call the function at all, supposing it knew the return type? Does it not evaluate left to right, see 0 * (unsigned int) and know the rvalue will be 0?

gator
  • 3,465
  • 8
  • 36
  • 76
  • 4
    The language requires that all operands be evaluated before applying the operation. Only logic operator (`&&` and `||`) perform short-circuit in C++. – Holt Dec 27 '19 at 18:02
  • Did you check the optimized code generated for main if you removing the printing? – Marc Glisse Dec 27 '19 at 18:07

2 Answers2

8

Short-circuit evaluation is mandatory for && (logical and), || (logical or) and ? (ternary operator). For the remaining operators it is an (optional) optimization.

The evaluation of fact(5) in the expression 0 * fact(5) can't be in general optimized away just because you know that the outcome of the whole expression is 0 since a call to fact() may introduce side effects (e.g., modify some global variable), so it must be called.

As said in this comment, a good compiler will optimize away the call to fact(5) if it can prove that there are no side effects.

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • 3
    A good compiler _will_ elide the call to `fact` if it can prove it has no side-effects. For example, GCC [does so](https://godbolt.org/z/QUGXsg) for OP's example if the `cout` is removed. – Miles Budnek Dec 27 '19 at 18:07
  • 3
    The discussion about side-effects is bit of a distraction, since short-circuited `||`/`&&` operands can be elided regardless of side-effects. – Lightness Races in Orbit Dec 27 '19 at 18:13
  • @LightnessRacesBY-SA3.0 Thanks, I've edited the answer accordingly. – JFMR Dec 27 '19 at 18:15
0

Does it not evaluate left to right, see 0 * (unsigned int) and know the rvalue will be 0?

It could, and it would if the standard told it to.

But it doesn't.

Short-circuiting simply isn't a thing for multiplication. They could have made it a thing, but it would arguably have been confusing.

We're all used to f() || g() potentially skipping the call to g(), but would you really expect 0 * g() to do the same thing? Particularly since 0 is just one out of billions of possible integers? It would be an oddly specific feature. (By contrast true is one out of only two boolean values.)

This isn't about side effects, because the side effects of g() would be skipped by f() || g() if f() returns true. That's just how it is.

In practice, a compiler could elide the call to g() in 0 * g() if it knew that g() had no side-effects (so the program's behaviour wouldn't be changed), but that's not short-circuiting; that's "optimisation".

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055