This question already has answers here:
Something we found when using comma in condition ternary operator? [duplicate]
(4 answers)
What's the precedence of comma operator inside conditional operator in C++?
(3 answers)
Closed 9 years ago.
I have no idea why the result of the two sub programs below are different:
int a , b;
a = 13, b=12;
(a > b)? (a++,b--):(a--,b++); // Now a is 14 and b is 11
a = 13, b=12;
(a > b)? a++,b-- : a--,b++; // Now a is 14 but b is 12
However for these cases, the results are identical:
a = 13, b=12;
(a < b) ? a++,b-- : a--,b++; // Now a is 12 and b is 13
a = 13, b=12;
(a < b) ? (a++,b--) : (a--,b++); // Again a is 12 and b is 13
Why parentheses make difference for the statement after "?", but make no difference for the statement after ":"? Do you have any idea?
This one:
(a > b)? a++,b-- : a--,b++;
is equivalent to:
((a > b) ? (a++, b--) : a--), b++;
so b is always incremented and only sometimes decremented. There is no way to parse the comma operator between ? and : other than as parenthesized in the 'equivalent to' expression. But after the :, the unparenthesized comma terminates the ternary ?: operator and leaves the increment as unconditionally executed. The precedence of the comma operator is very, very low.
The relevant parts of the C++ grammar are:
conditional-expression:
logical-or-expression
logical-or-expression ? expression : assignment-expression
assignment-expression:
conditional-expression
logical-or-expression assignment-operator assignment-expression
throw-expression
expression:
assignment-expression
expression, assignment-expression
In summary, while the 'middle' of a conditional expression can be a full expression extending up to the :, the last sub-expression can only be an assignment-expression which excludes expressions using the comma operator (other than where the comma operator appears as part of valid sub-expression of an assignment-expression such as a parenthesized primary-expression or as the second operand of another conditional-expression).
In C, the last sub-expression is more restricted, it cannot even be an assignment-expression although this is not a concern in your example.
conditional-expression:
logical-OR-expression
logical-OR-expression ? expression : conditional-expression
In this case
(a > b)? a++,b-- : a--,b++;
It is equivalent to
((a > b)? a++,b-- : a--),b++;
I guess it's because x ? y cannot be considered a valid expression, therefore the comma can't split the operator there. x ? y : z is a valid expression, the the comma after the colon can split into two expressions.
Related
The standard mentions f(a,(t=3,t+2),c); which would be an assignment-expression followed by an expression for the 2nd operator according to my understanding.
But the grammar lists it juxtaposed:
expression:
assignment-expression
expression, assignment-expression
Working Draft, Standard for Programming
Language C
++ Revision N4140 (November 2014)
Is someone so nice as to explain to me please what it is that I'm missing here?
When you see
expression:
assignment-expression
expression, assignment-expression
It mean that there are 2 possibilities for expression. One possibility that it is just assignment-expression that is defined somewhere earlier. Or it is recursively represented as expression, assignment-expression
So after extending it you receive that expression is comma separated list of one or more assignment-expression tokens.
In the sample you're mentioned second parameter is expression (t=3,t+2) which consists of 2 comma-separated assignment-expressions - and since it appears "In contexts where comma is given a special meaning" it has to "appear only in parentheses".
To find out why assignment-expression could take a form of t+2 you have to go back from its definitions and choose first choice always
assignment-expression
-> conditional-expression
--> logical-or-expression
---> logical-and-expression
----> inclusive-or-expression
-----> exclusive-or-expression
------> and-expression
-------> equality-expression
--------> relational-expression
---------> shift-expression
----------> additive-expression - this is what you see
Note that since the definition of expression is
expression:
assignment-expression
expression , assignment-expression
the second line means that any assignment-expression can be considered an expression, which is why t=3, t+2 is a valid expression.
So why is the grammar this way? First note that the grammar for expressions builds its way in steps from the most tightly bound category primary-expression to the least tightly bound category expression. (And then the fact that "( expression )" is a primary-expression brings the expression grammar full circle and lets us cause any expression to be more tightly bound than everything that surrounds it by adding parentheses.)
For example, the well-known fact that binary * binds tighter than binary + follows from these grammar pieces:
multiplicative-expression:
pm-expression
multiplicative-expression * pm-expression
multiplicative-expression / pm-expression
multiplicative-expression % pm-expression
additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression - multiplicative-expression
In the expression 2 + 3 * 4, the literals 2, 3, and 4 can be considered a pm-expression, or therefore also a multiplicative-expression or additive-expression. So you might say 2 + 3 would qualify as an additive-expression, but it is not a multiplicative-expression, so the full 2 + 3 * 4 can't work that way. Instead the grammar forces 3 * 4 to be considered a multiplicative-expression, so that 2 + 3 * 4 can be an additive-expression. Therefore 3 * 4 is a subexpression of the binary +.
Or in the expression 2 * 3 + 4, 3 + 4 might be considered an additive-expression, but then it is not a pm-expression, so that doesn't work. Instead the parser must recognize that 2 * 3 is a multiplicative-expression, which is also an additive-expression, so 2 * 3 + 4 is a valid additive-expression, with 2 * 3 as a subexpression of the binary +.
The recursive nature of most grammar definitions matters when the same operator is used twice, or two operators with the same precedence are used.
Going back to the comma grammar, if we have the tokens "a, b, c", we might say b, c could be an expression, but it is not an assignment-expression, so b, c cannot be a subexpression of the whole. Instead the grammar requires recognizing a, b as an expression, which is allowed as a left subexpression of another comma operator, so a, b, c is also an expression with a, b as the left operand.
This doesn't make any difference for the built-in comma, since its meaning is associative: "evaluate and discard a, then the result value comes from evaluating (evaluate and discard b, then the result value comes from evaluating c)" does the same as "evaluate and discard (evaluate and discard a, then the result value comes from evaluating b), then the result value comes from evaluating c".
But it does give us a clearly-defined behavior in case of an overloaded operator,. Given:
struct X {};
X operator,(X, X);
X a, b, c;
X d = (a, b, c);
we know that the last line means
X d = operator,(operator,(a,b), c);
and not
X d = operator,(a, operator,(b,c));
(I'd consider it particularly evil to define a non-associative operator,, but it is allowed.)
This is the syntax notation (see §1.6 of N4140).
It is mainly used to evaluate precedence, but the name can be misleading.
For example in [expr.ass] (§5.18) you have the folowing definition:
assignment-expression:
conditional-expression
logical-or-expression assignment-operator initializer-clause
throw-expression
assignment-operator: one of
= *= /= %= += -= >>= <<= &= ^= |=
So an assignment-expression can be a conditional-expression or a throw-expression even if neither performs any assignment.
This just states that a = b, throw 10 or cond ? c : d are expressions with the same precedence order.
f(a,(t=3,t+2),c);
Here, first, stores 3 into t variable, then calls function f() with three arguments. It means second argument value become 5 and pass to the function.
For an expression like
x = a ? b : c ? d : e;
I understand that because the ?: operator has right associativity, the expression is grouped as
x = a ? b : (c ? d : e);
However, what about order of evaluation? Does associativity mean that the (c ? d : e) branch evaluated first, and then the answer of it passed as an argument to the left ?: operator? Or is a evaluated first, and then depending on that either b is returned or the (c ? d : e) branch is evaluated? Or is it undefined?
n3376 5.16/1
Conditional expressions group right-to-left. The first expression is
contextually converted to bool (Clause 4). It is evaluated and if it
is true, the result of the conditional expression is the value of the
second expression, otherwise that of the third expression. Only one of
the second and third expressions 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 or third expression.
For the conditional operator:
the first operand is evaluated first;
either the second or the third (but not both) is evaluated depending on the value of the first.
Consider the following codes:
int a = 3;
int b = 0;
b = a > 0 ? ++b, ++a : --a, b = 0;
After execution, I get the value of b to become 0 and the value of a to become 4.
That means the result of condition expression, a > 0 is evaluated as true and the expression a++ has been executed, while the expression b = 0 after , hast been executed ,too. In other words, the expression b = 0 is not an operand of the ternary operator, while ++b is. Otherwise, b = 0 won't be executed since the condition expression isn't evaluated as false.
My question is "according to what rule does the compiler kick b = 0 out of the ternary operator's operand?"
The operators in the third statement includes: ++ and --, which have the highest precedence, >, which has the second largest precedence, ? : and =, which have the third largest precedence and , with the lowest precedence. I know that operators with higher precedence should determine their operands earlier so that ++,--, and > are handled first. Then the statement is equivalently:
b = (a > 0) ? (++b), (++a) : (--a), b = 0;
Now, it's = and ?:'s turn to be handled. The associativity of = and ?: is right-to-left, so I consider the compiler will parse the statement from the right end.The first operator met is = and so b = 0 is grouped together. The second met operator is ,. Since it's precedence is lower then the current operators being analyzed, I assume the compiler will just skip it. Then the compiler met :, which is a part of ternary operator, so it keeps parsing.(Actually I don't know how the compiler can know that : is a part of ?: before parsing the whole ternary operator) Problem comes here. The next operator met by the compiler is , but the compiler haven't finished determining the operands of ?: yet. The , has lower priority than ?:. Theoretically it should be skipped; surprisingly, in practical test, the (++b) and (++a) have been concatenated by the , operator at this time and both are considered as the operand of ?:. That makes me confused. Why does the last , is ignored and doesn't included in the operand of ?: while the previous , in statement is kept in the operand of ternary operator?
May someone clarify the concepts of precedence and associativity with this example? I'm really confused about the executing result when first taking a sight of this piece of codes. I had thought that the expression b=0 is also a part of the ternary operator's operand; therefore b = 0 will only be executed if a > 0 is false.
Thanks in advance.
Precedence and associativity are different concepts, but technically the C and C++ standard specifies none. Instead they give the grammar rules to deduce the structure of the expression.
The relevant rules are:
conditional-expression:
logical-or-expression
logical-or-expression ? expression : assignment-expression
expression:
assignment-expression
expression , assignment-expression
primary-expression:
( expression )
postfix-expression:
primary-expression
...
And so on...
The idea is that each type of expression can generate a composite expresion or another type of expression of lower precedence. You can only go up to the root expression by using parenthesis.
With that in mind, note that the conditional-expression that uses the ?: actually has different types of expressions in each of the three subexpressions. The middle one is expression so it will accept any kind of expression, even with , or = (no ambiguity here because of the ending :).
But note that the last one is assignment-expression, that is any kind of expression except the one with ,. If you want to use that, you will have to enclose it with () creating a primary-expression instead.
Bonus explanation: the first expression is logical-or-expression, and if you look carefully to the grammar you'll see that it excludes assignment operators, the conditional operator and the comma operator.
So your expression:
b = a > 0 ? ++b, ++a : --a, b = 0
Is actually a expression comma assignment-expression, where the first expression is b = a > 0 ? ++b, ++a : --a and the second assignment-expression is b = 0.
And so on...
Your expression is evaluated as (b = ((a > 0) ? (++b, ++a) : (--a))), (b = 0);.
As you say the ?: has higher precedence than the comma operator, so the b=0 does not belong to the ternary conditional. The difference for the left and the right part of the ternary operator is, that on the left side the compiler tries to evaluate the complete string ++b, ++a as an expression (knowing that the part between ? and : must be an expression, while on the right side the compiler tries to parse an expression as far as it can. And precedence of operators says the compiler must stop at the ,. On the left side the compiler does not stop on the , because this is a legal part of the expression.
I read somewhere that the ?: operator in C is slightly different in C++, that there's some source code that works differently in both languages. Unfortunately, I can't find the text anywhere. Does anyone know what this difference is?
The conditional operator in C++ can return an lvalue, whereas C does not allow for similar functionality. Hence, the following is legal in C++:
(true ? a : b) = 1;
To replicate this in C, you would have to resort to if/else, or deal with references directly:
*(true ? &a : &b) = 1;
Also in C++, ?: and = operators have equal precedence and group right-to-left, such that:
(true ? a = 1 : b = 2);
is valid C++ code, but will throw an error in C without parentheses around the last expression:
(true ? a = 1 : (b = 2));
The principal practical difference is that in C, evaluation of ?: can never result in a l-value where as in C++ it can.
There are other differences in its definition which have few practical consequences. In C++ the first operand is converted to a bool, in C it is compared against 0. This is analagous to the difference in definition of ==, !=, etc. between C and C++.
There are also more complex rules in C++ for deducing the type of a ?: expression based on the types of the 2nd and 3rd operands. This reflects the possibility of user-defined implicit conversions in C++.
Example code. Valid C++; invalid C.
extern int h(int p, int q);
int g(int x)
{
int a = 3, b = 5;
(x ? a : b) = 7;
return h( a, b );
}
gcc generates the error: "error: invalid lvalue in assignment" when compiling as C, but the code compiles without error when compiling as C++.
Edit:
Although ?: can't return an l-value in C, perhaps surprisingly the grammar for ?: is:
conditional-expression:
logical-OR-expression
logical-OR-expression ? expression : conditional-expression
This means that a ? b : c = d parses as (a ? b : c) = d even though (due to the 'not an l-value' rule) this can't result in a valid expression.
C++ changes the grammar to this:
conditional-expression:
logical-or-expression
logical-or-expression ? expression : assignment-expression
While the extension to allow conditional-expression to be an l-value in some situations would have made a ? b : c = d valid without the grammar change, the new grammar change means that the expression is now valid but with the different meaning of a ? b : (c = d).
Although I don't have any evidence for it, my supposition that as the grammar change couldn't break compatibility with existing C code, it was more likely that the new grammar would produce fewer surprises with expressions such as:
make_zero ? z = 0 : z = 1;
This question already has answers here:
What's the precedence of comma operator inside conditional operator in C++?
(3 answers)
Closed 9 years ago.
Well, I had a question about comma in ternary operator.
Cut the crap, the code is below:
void test_comma_in_condition(void)
{
int ia, ib, ic;
ia = ib = ic = 0;
bool condition=true;
cout<<"Original:"<<endl;
cout<<"ia: "<<ia<<endl;
cout<<"ib: "<<ib<<endl;
condition?(ia=1, ib=2):(ia=11, ib=12);
cout<<"After:"<<endl;
cout<<"ia: "<<ia<<endl;
cout<<"ib: "<<ib<<endl;
ia = ib = ic = 0;
condition?ia=1, ib=2, ic=3:ib=22,ia=21, ic=23;
cout<<"The operation must be bracketed, or you'll see..."<<endl;
cout<<"ia: "<<ia<<endl;
cout<<"ib: "<<ib<<endl;
cout<<"ic: "<<ic<<endl;
condition?ia=1, ib=2, ic=3:ia=21, ib=22, ic=23;
cout<<"The operation must be bracketed, or you'll see..."<<endl;
cout<<"ia: "<<ia<<endl;
cout<<"ib: "<<ib<<endl;
cout<<"ic: "<<ic<<endl;
return;
}
The output will be like:
Original:
ia: 0
ib: 0
After:
ia: 1
ib: 2
The operation must be bracketed, or you'll see...
ia: 21
ib: 2
ic: 23
The operation must be bracketed, or you'll see...
ia: 1
ib: 22
ic: 23
Is this legal?
This is a matter of operator precedence. Your expression:
condition?ia=1, ib=2, ic=3:ib=22,ia=21, ic=23;
is understood by the compiler as:
(condition?(ia=1, ib=2, ic=3):(ib=22)),ia=21, ic=23;
At this point you should be able to see why you get the program output.
Yes, the relevant grammar for a conditional expression is:
logical-or-expression ? expression : assignment-expression
for assignment expressions (which can also be a conditional-expression or a throw-expression):
logical-or-expression assignment-operator assignment-expression
and for an expression with a comma operator (an assignment-expression can also be an expression):
expression , assignment-expression
This means that the construct a ? b : c, d cannot be parsed as equivalent to a ? b : (c, d) because c, d is not an assignment-expression but must be parsed as equivalent to (a ? b : c), d.
There is no undefined behaviour in condition ? ia=1,ib=2,ic=3 : ia=21, ib=22, ic=23; because evaluation of condition is sequenced before the evaluation of either the second or third operands of ?: and in every sub-expression containing a comma operator the evaluation of the first operand of the comma operator is sequenced before the evaluation of the second operand.
It's legal, but stupid not very useful to write code like that.
The code
condition?ia=1, ib=2, ic=3:ia=21, ib=22, ic=23;
is equivalent to
condition?(ia=1, ib=2, ic=3):ia=21;
ib=22;
ic=23;
just harder to read.
The problem is that the comma operator has the lowest precedence there is. Thanks to that, the else part of the conditional operator is just the first assignment, after that the comma operator kicks in and the other two statements will be executed just aswell.