27

Here's an example

#include <iostream>
using namespace std;
int main()
{   
    int x = 0;
    cout << (x == 0 ? x++ : x) << endl; //operator in branch
    cout << "x=" << x << endl;
    cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
    cout << "x=" << x << endl;
    return 0;
}

output:

0
x=1
1
x=1

I understand the output, but is this undefined behaviour or not? Is the order of evaluation guaranteed in either case?

Even if guaranteed, I'm quite aware using increment/decrement can quickly become an issue for readability. I only ask as I saw similar code and was immediately unsure, given there are lots of examples of ambiguous/undefined use of increment/decrement operators, such as...

  • C++ does not define the order in which function parameters are evaluated.

    int nValue = Add(x, ++x);
    
  • The C++ language says you cannot modify a variable more than once between sequence points.

     x = ++y + y++
    
  • Because increment and decrement operators have side effects, using expressions with increment or decrement operators in a preprocessor macro can have undesirable results.

     #define max(a,b) ((a)<(b))?(b):(a)
     k = max( ++i, j );
    
jozxyqk
  • 16,424
  • 12
  • 91
  • 180
  • 2
    Related (not duplicate): http://stackoverflow.com/questions/10995445/ternary-operator-and-sequence-points-in-c - Describes a special case of assigning back into the incremented variable. – Suma Sep 08 '14 at 13:59
  • Note: Well-defined is only one question. Maintainable is another. If you have to ask us, how's the next person who reads that code going to be sure it's safe? "Real writers rewrite to avoid the problem." – keshlam Sep 09 '14 at 03:12
  • The decrement operator in line 4 of `main()` is irrelevant in this example because the short-circuit behavior of `||` will cause the `--x` to be skipped entirely. – JLRishe Sep 09 '14 at 07:43
  • @JLRishe actually the question hinges around it: is the short-circuit guaranteed such that `--x` is never evaluated? (Answered below) – jozxyqk Sep 09 '14 at 07:53
  • 1
    @jozxyqk Ok, but in that case, it has nothing to do with ternary/conditional operators and everything to do with the `||` operator. `x == 1 || --x == 0` is fully evaluated before the conditional operator has any involvement and by that time, the `--x` will have already been skipped. In other words, line 4 doesn't tell us anything non-trivial about conditional operators. – JLRishe Sep 09 '14 at 07:55
  • it's safe, yet the effects can be unclear... --X is evaluated before the expression is evaluated and X-- is evaluated after the expression is evaluated. – Paul Bastide Sep 30 '14 at 14:35

5 Answers5

34

For the conditional operator (§5.16 [expr.cond]/p1):

Every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second or third expression.

For the logical OR operator (§5.15 [expr.log.or]/p1-2):

the second operand is not evaluated if the first operand evaluates to true. [...] If the second expression is evaluated, every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second expression.

The behavior of your code is well-defined.

T.C.
  • 133,968
  • 17
  • 288
  • 421
10

There is a guaranteed order of execution in ternary operators and boolean && and || operations, so there is no conflict in evaluation sequence points.

One at a time

 cout << (x == 0 ? x++ : x) << endl; //operator in branch

Will always output x but will increment it only if it was 0.

 cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

This is well defined too, if x was 1 it will not evaluate the RHS, if it wasn't it will decrement it but --x will never be 0, so it will be true iff x==1, in which case x will also now be 0.

In the latter case if x is INT_MIN it is not well-defined behaviour to decrement it (and it would execute).

That can't happen in the first case where x won't be 0 if it is INT_MAX so you are safe.

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • Edge case, but of course the second code snippet would have UB if `x` is `INT_MIN` when it's executed. The equivalent problem with `INT_MAX` can't affect the first snippet. – Steve Jessop Sep 08 '14 at 14:06
8

I understand the output, but is this undefined behaviour or not?

Code is perfectly defined. C11 standard says:

6.5.15 Conditional operator

The first operand is evaluated; there is a sequence point between its evaluation and the evaluation of the second or third operand (whichever is evaluated). The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below.110)

6.5.14 Logical OR operator

Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares unequal to 0, the second operand is not evaluated.

Further wiki explains it with example:

  • Between evaluation of the left and right operands of the && (logical AND), || (logical OR) (as part of short-circuit evaluation), and comma operators. For example, in the expression *p++ != 0 && *q++ != 0, all side effects of the sub-expression *p++ != 0 are completed before any attempt to access q.

  • Between the evaluation of the first operand of the ternary "question-mark" operator and the second or third operand. For example, in the expression a = (*p++) ? (*p++) : 0 there is a sequence point after the first *p++, meaning it has already been incremented by the time the second instance is executed.

The rule for || and ?: is same for C++ (section 5.15 and 5.16) as in C.


Is the order of evaluation guaranteed in either case?

Yes. The order of evaluation of the operands of operators ||, &&, , and ?: is guaranteed to be from left to right.

haccks
  • 104,019
  • 25
  • 176
  • 264
  • 2
    @Slava; Oh! How it is misleading ? Could you explain ? – haccks Sep 08 '14 at 14:00
  • There are 2 examples in the code, second one has UB imo. I could be wrong, but answer does not answer it anyway, as it is based on if boolean operator has sequence point. – Slava Sep 08 '14 at 14:00
  • 2
    @Slava; Which one has UB ? – haccks Sep 08 '14 at 14:04
  • @haccks there is no UB I was wrong. But your answer does not mention why second example does not have UB, ie there a sequence point in boolean operator || – Slava Sep 08 '14 at 14:09
3

In C an object's stored value can be modified only once between two sequence points.

A sequence point occurs:

  1. At the end of full expression.
  2. At the &&, || and ?: operators
  3. At a function call.

So for example this expression x = i++ * i++ is undefined, whereas x = i++ && i++ is perfectly legal.

Your code shows defined behaviour.

int x=0;

cout << ( x == 0 ? x++ : x) << endl;

In the above expression x is 0, so x++ will be executed, here x++ is post increment so it will output 0.

cout << "x=" << x << endl;

In the above expression as x now has value 1 so the output will be 1.

cout << (x == 1 || --x == 0 ? 1 : 2) << endl;

Here x is 1 so the next condition is not evaluated(--x == 0) and the output will 1.

cout << "x=" << x << endl;

As the expression --x == 0 is not evaluated the output will again be 1.

Community
  • 1
  • 1
ani627
  • 5,578
  • 8
  • 39
  • 45
2

Yes, it is safe to use the increment/decrement operators as you have. Here is what is happening in your code:

Snippet #1

cout << (x == 0 ? x++ : x) << endl; //operator in branch

In this snippet, you are testing if x == 0, which is true. Since it is true, your ternary expression evaluates the x++. Since you are using a post-increment here, the original value for x is printed to the standard output stream, and then x is incremented.

Snippet #2

cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

This snippet is a bit more confusing, but it still yields a predictable result. At this point, x = 1 from the first snippet. In the ternary expression, the conditional part is evaluated first; however, due to Short-Circuiting, the second condition, --x == 0, is never evaluated.

For C++ the operators || and && are the short-circuiting boolean operators for logical OR and logical AND respectively. When you use these operators, your conditions are checked (from left to right) until the final result can be determined. Once the result is determined, no more conditions are checked.

Looking at snippet #2, your first condition checks if x == 1. Since your first condition evaluates to true and you are using the logical OR, there is no need to keep evaluating other conditions. That means that --x == 0 is never executed.


A quick side-note about short-circuiting:

Short-circuiting is useful for increasing performance in your program. Suppose you had a condition like this which calls several time-expensive functions:

if (task1() && task2())
{ 
    //...Do something...
}

In this example, task2 should never be called unless task1 completes successfully (task2 depends on some data that is altered by task1).

Because we are using a short-circuiting AND operator, if task1 fails by returning false, then if-statement has the sufficient information to exit early and stop checking other conditions. This means that task2 is never called.

Community
  • 1
  • 1
Nicholas Miller
  • 4,205
  • 2
  • 39
  • 62