Why does the ternary operator with commas evaluate only one expression in the true case?
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
.
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:
someValue?
-
(++x,++y)
or--x
(depending onbool
result 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
?
-
(someValue?(++x,++y):--x)
executes and incrementsx
andy
to be11
and11
- The left expression is discarded (though the side effects of increment remain)
- We evaluate the right hand side of the comma operator:
--y
, which then decrementsy
back to10
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)
Your problem is that the ternary expression doesn't really have higher precedence than comma. In fact, C++ can't be described accurately simply by precedence - and it is exactly the interaction between the ternary operator and comma where it breaks down.
a ? b++, c++ : d++
is treated as:
a ? (b++, c++) : d++
(comma behaves as if it has higher precedence). On the other hand,
a ? b++ : c++, d++
is treated as:
(a ? b++ : c++), d++
and the ternary operator is higher precedence.
A point that's been overlooked in answers (though touched on comments) is that the conditional operator is invariably used (intended by design?) in real code as a shortcut for assigning one of two values to a variable.
So, the larger context would be:
whatIreallyWanted = someValue ? ++x, ++y : --x, --y;
Which is absurd on its face, so the crimes are manifold:
- The language permits ridiculous side effects in an assignment.
- The compiler didn't warn you that you were doing bizarre things.
- The book appears to be focusing on 'trick' questions. One can only hope that the answer in the back was "What this expression does is depend on weird edge cases in a contrived example to produce side effects that nobody expects. Never do this."