Rectangle 27 1

c++11 In which versions of the C++ standard does "(i+=10)+=10" have undefined behaviour?


[expr.ass]/1

... maybe it's worthy to mention that because the result of i+=10 is lvalue (i.e the "i" object itself) the modification of i cannot be treated as "independed" side effect (which is not sequenced with anything until final ";" )

@PaulR In C++98 the paragraph says "The result of the assignment operation is the value stored in the left operand after the assignment has taken place;" I'm not sure if that technically means there's a sequence point between assignments. At the very least I think this is under-specified in C++98.

@PaulR: C++11 only. C++03 had no notion of "sequenced after", it used sequence points. And in fact, in C++03 it is undefined, because there's no intervening sequence point between the assignments.

In C++03 the expression has undefined behavior, because it causes i to be modified twice with no intervening sequence point.

In C++11 the expression is well defined and will result in i == 20.

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.

Thanks - this should really be clarified in the answer then, as the question does not specify C++11.

This means that the assignment i+=1 is sequenced before the value computation of the left hand side of (i+=10)+=10, which is in turn sequenced before the final assignment to i.

Note
Rectangle 27 1

c++11 In which versions of the C++ standard does "(i+=10)+=10" have undefined behaviour?


"whereas in C++98 the value assigned to j is indeterminate but the behavior is not undefined" - this is incorrect. C++98 says that the previous value of "i" shall only be read to determine the value to be stored in a modification when there's no intervening sequence point. But in "(i+=1) + i", it is unspecified whether the "+ i" reads the previous or the next value of "i", so the consequence is there's undefined behavior.

(And it seems to me that the extra flexibility afforded by 'sequence before' and 'sequenced after' has lead to a much more clear, consistent, and well specified language.)

@JohannesSchaub-litb Could you give me a citation so I can see it in the C++98 standard in context?

@JohannesSchaub-litb However, if the intended meaning is "only the prior value shall be accessed" then does that mean that there's no requirement to access the prior value "only to determine the value to be stored?" I.e. am I allowed to access the prior value for other purposes?

@JohannesSchaub-litb I think you're probably right that the language was intended to mean that and that the requirement I had thought was new in C++11 isn't actually new. But the C++98 language definitely needed cleaning up because as written, "the prior value shall be accessed only to [...]," does not mean the same thing as "only the prior value shall be accessed."

Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression.

C++11 does away sequence points for the much clearer idea of sequence-before and sequenced-after. The language from C++98 is replaced with

C++11 doesn't have sequence points and only requires that the modifications of an object be ordered with respect to each other and to reads of the same object to produce defined behavior.

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...]

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expression, and the order in which side effects take place, is unspecified.

If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

In C++98 multiple modifications to the same object without an intervening sequence-point results in undefined behavior, even when the order of those modifications is well specified. This expression does not contain any sequence points and so the fact that it consists of two modifications is sufficient to render its behavior undefined.

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.

It seems unlikely to me that any C++98 implementation would actually do anything surprising when the sequence of operations is well specified even if that is insufficient to produce technically well defined behavior. As an example, the internal representation of this expression produced by Clang in C++98 mode has well defined behavior and does the expected thing.

So I believe the order is specified, however I don't see that that alone is enough to create a sequence point in the middle of an expression. And continuing on with the quote of [expr] 5 p4:

So even though the order is specified it appears to me that this is not sufficient for defined behavior in C++98.

So while being ordered was not sufficient to make the behavior defined in C++98, C++11 has changed the requirement such that being ordered (i.e., sequenced) is sufficient.

The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue

Therefore the behavior is undefined in C++98 but well defined in C++11.

it is right after the text you quoted that ends with "... shall have its stored value modified at most once by the evaluation of an expression.".

tl;dr: The sequence of the modifications and reads performed in (i+=10)+=10 is well defined in both C++98 and C++11, however in C++98 this is not sufficient to make the behavior defined.

Note
Rectangle 27 1

c++11 In which versions of the C++ standard does "(i+=10)+=10" have undefined behaviour?


@Fanael: I know you're just guessing, but I don't follow your logic. If the assignment operators did not return an lvalue, then neither f(x = 5) nor f(x += 5) would compile (for f wanting a int&). How would that be any less consistent?

@Mankarse The given justification (which I don't really agree with) is to support things like int& f(int& i) { return i += 2; }

@Mankarse: presumably for consistency with simple assignment, so you can do both f(x = 5) and f(x += 5) with f wanting a T&.

In C++03, it's an obvious UB, there's no intervening sequence point between the assignments.

In C++11, as Mankarse explains, it's not undefined anymore the parenthesized compound assignment is sequenced before the outer one, so it's okay.

The C++03 answer raises a new question -- why did the assignment operator return an lvalue in C++03 if it was indeed undefined to modify its result?

Note