Why does the ternary operator with commas evaluate only one expression in the true case?

Aufziehvogel picture Aufziehvogel · Nov 28, 2017 · Viewed 7.7k times · Source

I'm currently learning C++ with the book C++ Primer and one of the exercises in the book is:

Explain what the following expression does: someValue ? ++x, ++y : --x, --y

What do we know? We know that the ternary operator has a higher precedence than the comma operator. With binary operators this was quite easy to understand, but with the ternary operator I am struggling a bit. With binary operators "having higher precedence" means that we can use parentheses around the expression with higher precedence and it will not change the execution.

For the ternary operator I would do:

(someValue ? ++x, ++y : --x, --y)

effectively resulting in the same code which does not help me in understanding how the compiler will group the code.

However, from testing with a C++ compiler I know that the expression compiles and I do not know what a : operator could stand for by itself. So the compiler seems to interpret the ternary operator correctly.

Then I executed the program in two ways:

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

Results in:

11 10

While on the other hand with someValue = false it prints:

9 9

Why would the C++ compiler generate code that for the true-branch of the ternary operator only increments x, while for the false-branch of the ternary it decrements both x and y?

I even went as far as putting parentheses around the true-branch like this:

someValue ? (++x, ++y) : --x, --y;

but it still results in 11 10.

Answer

AndyG picture AndyG · Nov 28, 2017

As @Rakete said in their excellent answer, this is tricky. I'd like to add on to that a little.

The ternary operator must have the form:

logical-or-expression ? expression : assignment-expression

So we have the following mappings:

  • someValue : logical-or-expression
  • ++x, ++y : expression
  • ??? is assignment-expression --x, --y or only --x?

In fact it is only --x because an assignment expression cannot be parsed as two expressions separated by a comma (according to C++'s grammar rules), so --x, --y cannot be treated as an assignment expression.

Which results in the ternary (conditional) expression portion to look like this:

someValue?++x,++y:--x

It may help for readability's sake to consider ++x,++y to be computed as-if parenthesized (++x,++y); anything contained between ? and : will be sequenced after the conditional. (I'll parenthesize them for the rest of the post).

and evaluated in this order:

  1. someValue?
  2. (++x,++y) or --x (depending on boolresult of 1.)

This expression is then treated as the left sub-expression to a comma operator, with the right sub-expression being --y, like so:

(someValue?(++x,++y):--x), --y;

Which means the left side is a discarded-value expression, meaning that it is definitely evaluated, but then we evaluate the right side and return that.

So what happens when someValue is true?

  1. (someValue?(++x,++y):--x) executes and increments x and y to be 11 and 11
  2. The left expression is discarded (though the side effects of increment remain)
  3. We evaluate the right hand side of the comma operator: --y, which then decrements y back to 10

To "fix" the behavior, you can group --x, --y with parentheses to transform it into a primary expression which is a valid entry for an assignment-expression*:

someValue?++x,++y:(--x, --y);

*It's a rather funny long chain that connects an assignment-expression back to a primary expression:

assignment-expression ---(can consist of)--> conditional-expression --> logical-or-expression --> logical-and-expression --> inclusive-or-expression --> exclusive-or-expression --> and-expression --> equality-expression --> relational-expression --> shift-expression --> additive-expression --> multiplicative-expression --> pm-expression --> cast-expression --> unary-expression --> postfix-expression --> primary-expression