Double assignment of the same variable in one expression in C++11
Asked Answered
G

3

13

The C++11 standard (5.17, expr.ass) states that

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation

As I understand it, all expressions which are a part of the given assignment will be evaluated before the assignment itself. This rule should work even if I modify the same variable twice in the same assignment, which, I am fairly certain, was undefined behavior before.

Will the given code:

int a = 0;
a = (a+=1) = 10;

if ( a == 10 ) {
    printf("this is defined");
} else {
    printf("undefined"); 
}

always evaluate to a==10?

Greenfinch answered 25/10, 2013 at 10:6 Comment(3)
Side note: Checking the result of a possibly undefined expression doesn't tell us whether it is actually UB or not. It can be UB and produce correct result.Archaimbaud
@Archaimbaud that's a sample code, perhaps even an SSCCE, as SO requires me to include valid code. I did try testing it, just out of curiosity, but I realize it proves nothing; hence I didn't even mention it.Greenfinch
@Archaimbaud - it can produce "correct" result. The quotes are important. <g>Selfidentity
P
7

Let's rewrite your code as

E1 = (E2 = E3)

where E1 is the expression a, E2 is the expression a += 1 and E3 is the expression 10. Here we ussed, that the assignment operator groups right-to-left (§5.17/1 in C++11 Standard).

§5.17/1 moreover states:

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

Applying this to our expression means that we first must evaluate the subexpressions E1 and E2 = E3. Note that there is no "sequenced-before" relationship between these two evaluations, but that causes no problems.

The evaluation of the id-expression E1 is trivial (the result is a itself). The evaluation of the assignment-expression E2 = E3 proceeds as follows:

First both subexpressions have to be evaluated. The evaluation of the literal E3is again trivial (gives a prvalue of value 10).

The evaluation of the (compound) assignment-expression E2 is done in the following steps:

1) The behavior of a += 1is equivalent to a = a + 1 but a is only evaluated once (§5.17/7). After evaluating the subexpressions a and 1 (in an arbitrary order), an lvalue-to-rvalue conversion is applied to a in order to read the value stored in a.

2) The values of a (which is 0) and of 1 are added (a + 1) and the result of this addition is a prvalue of value 1.

3) Before we can compute the result of the assignment a = a + 1 the value of the object the left operand refers to is replaced by the value of the right operand (§5.17/2). The result of E2 is then an lvalue refereing to the new value 1. Note that the side effect (updating the value of the left operand) is sequenced before the value computation of the assignment expression. This is §5.17/1 cited above.

Now that we have evaluated the subexpressions E2and E3, the value of the expression E2refers to is replaced by the value of E3, which is 10. Hence the result of E2 = E3 is an lvalue of value 10.

Finally, the value expression E1 refers to is replaced by the value of the expression E2 = E3, which we computed to be 10. Thus, the variable aends up to contain the value 10.

Since all these steps are well-defined, the whole expression yields a well-defined value.

Preform answered 30/10, 2013 at 12:54 Comment(0)
B
8

Yes, there was a change between C++98 and C++11. I believe your example to be well-defined under C++11 rules, while exhibiting undefined behavior under C++98 rules.

As a simpler example, x = ++x; is undefined in C++98 but is well-defined in C++11. Note that x = x++; is still undefined (side effect of post-increment is unsequenced with the evaluation of the expression, while side effect of pre-increment is sequenced before the same).

Bawcock answered 25/10, 2013 at 16:34 Comment(2)
That is interesting and good to know, though you unfortunately do not address the code posted. Do you have any thoughts about it being undefined/unspecified behavior?Greenfinch
I believe your example is well-defined. Updated the answer accordingly.Bawcock
P
7

Let's rewrite your code as

E1 = (E2 = E3)

where E1 is the expression a, E2 is the expression a += 1 and E3 is the expression 10. Here we ussed, that the assignment operator groups right-to-left (§5.17/1 in C++11 Standard).

§5.17/1 moreover states:

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

Applying this to our expression means that we first must evaluate the subexpressions E1 and E2 = E3. Note that there is no "sequenced-before" relationship between these two evaluations, but that causes no problems.

The evaluation of the id-expression E1 is trivial (the result is a itself). The evaluation of the assignment-expression E2 = E3 proceeds as follows:

First both subexpressions have to be evaluated. The evaluation of the literal E3is again trivial (gives a prvalue of value 10).

The evaluation of the (compound) assignment-expression E2 is done in the following steps:

1) The behavior of a += 1is equivalent to a = a + 1 but a is only evaluated once (§5.17/7). After evaluating the subexpressions a and 1 (in an arbitrary order), an lvalue-to-rvalue conversion is applied to a in order to read the value stored in a.

2) The values of a (which is 0) and of 1 are added (a + 1) and the result of this addition is a prvalue of value 1.

3) Before we can compute the result of the assignment a = a + 1 the value of the object the left operand refers to is replaced by the value of the right operand (§5.17/2). The result of E2 is then an lvalue refereing to the new value 1. Note that the side effect (updating the value of the left operand) is sequenced before the value computation of the assignment expression. This is §5.17/1 cited above.

Now that we have evaluated the subexpressions E2and E3, the value of the expression E2refers to is replaced by the value of E3, which is 10. Hence the result of E2 = E3 is an lvalue of value 10.

Finally, the value expression E1 refers to is replaced by the value of the expression E2 = E3, which we computed to be 10. Thus, the variable aends up to contain the value 10.

Since all these steps are well-defined, the whole expression yields a well-defined value.

Preform answered 30/10, 2013 at 12:54 Comment(0)
C
4

After doing a little research, I am convinced your codes behaviour is well defined in C++11.

$1.9/15 states:

The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.

$5.17/1 states:

The assignment operator (=) and the compound assignment operators all group right-to-left.

If I understand correctly, in your example

a = (a+=1) = 10;

this implies that the value computations of (a+=1) and 10 have to be made before the value computation of (a+=1) = 10 and the value computation of this expression has to be finished before a = (a+=1) = 10; is evaluated.

$5.17/1 states:

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

This implies that the assignment must happen before the value computation, and therefore, due to transitivity, the evaluation of (a+=1) = 10 can only begin after the assignment a+=1 (Because its value may only be computed after the side effect).

The same is true for the second and third assignment.

See also this excellent answer, which explains the sequenced-before relation in much more detail and way better than I could.

Catalano answered 29/10, 2013 at 22:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.