ChatGPT解决这个技术问题 Extra ChatGPT

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.

"Precedence" is just an emergent phenomenon in C++. It may be simpler to just look at the language grammar directly and see how expressions work.
We don't care that much about the principles. :-) The fact that you have to ask this here indicates the code is never going to pass a code review by your fellow programmers. That makes the knowledge about how this actually works less than useful. Unless you want to participate in the Obfuscated C Contest, of course.
@BoPersson without examples like this to learn from, future reviewers will never learn why they should reject this from production.
@Leushenko - The warning bells should be ringing anyway. Multiple increments and decrements in the same statement (ding, ding, ding!). A ternary operator when you could use if-else (ding, ding, ding!). Wait, are those commas the dreaded comma operator? (ding, DING, DING!) With all those operators, could there be some precedence thing? (ding, ding, ding!) So we are never going to be able to use that. Then why waste time figuring out what it does, if anything?
Minor nit: the name for ? is the conditional operator. The term ternary operator simply means an operator with three operands. The conditional operator is one example of a ternary operator, but a language could (theoretically) have multiple ternary operators.

A
AndyG

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

(someValue?(++x,++y):--x) executes and increments x and y to be 11 and 11 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 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


Thanks for taking the trouble of unraveling the grammar rules; doing so shows that there is more to C++'s grammar than you will find in most textbooks.
@sdenham: When people ask why "expression oriented languages" are nice (ie when { ... } can be treated as an expression), I now have an answer => it's to avoid having to introduce a comma operator which behaves in such tricky ways.
Could you give me a link to read about the assignment-expression chain?
@MiP: I lifted it from the standard itself, you can find it under gram.expr
P
Pedro A

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.


C
Community

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)


M
Martin Bonner supports Monica

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.


I think this is still within the realm of precedence since there is only one valid parse for the middle line, right? Still a helpful example though
T
Taryn

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


Assigning one of two variables is the usual case of the ternary operator, but there are occasions when it is useful to have an expression form of if (for example, the increment expression in a for loop). The larger context might well be for (x = 0, y=0; x+y < 100; someValue?(++x, ++y) :( --x, --y)) with a loop that can modify x and y independently.
@MartinBonner I'm not convinced, and this example seems to make bo-perrson's point pretty well, as he quoted Tony Hoare.