简体   繁体   中英

Is it safe to put increment/decrement operators inside ternary/conditional operators?

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 ); 

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.

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.

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 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.

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 .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM