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

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


Wow, that's tricky.

The compiler sees your expression as:

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

The ternary operator needs a :, it cannot stand by itself in that context, but after it, there is no reason why the comma should belong to the false case.

Now it might make more sense why you get that output. If someValue is true, then ++x, ++y and --y get executed, which doesn't effectively change y but adds one to x.

If someValue is false, then --x and --y are executed, decrementing them both by one.


Why would the C++ compiler generate code that for the true-branch of the ternary operator only increments x

You misinterpreted what has happened. The true-branch increments both x and y. However, y is decremented immediately after that, unconditionally.

Here is how this happens: since the conditional operator has higher precedence than comma operator in C++, the compiler parses the expression as follows:

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

Note the "orphaned" --y after the comma. This is what leads to decrementing y that has been initially incremented.

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

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

You were on the right path, but you parenthesized a wrong branch: you can fix this by parenthesizing the else-branch, like this:

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

Demo (prints 11 11)