The phrase, "…the final value of i
will be 4 no matter what the order of evaluation…" is incorrect. The compiler could emit the equivalent of this:
i = 3;
int tmp = i;
++i;
i = tmp;
or this:
i = 3;
++i;
i = i - 1;
or this:
i = 3;
i = i;
++i;
As to the definitions of terms, if the answer was guaranteed to be 4, that wouldn't be unspecified or undefined behavior, it would be defined behavior.
As it stands, it is undefined behaviour according to the standard (Wikipedia), so it's even free to do this:
i = 3;
system("sudo rm -rf /"); // DO NOT TRY THIS AT HOME … OR AT WORK … OR ANYWHERE.
It's "undefined behavior," not "unspecified." Undefined means the machine is allowed to do anything including output an empty program, terminate randomly, or explode. Of course, a subtly unexpected value when porting to another platform is a more likely outcome.
Undefined behavior applies to any case where two side effects apply to the same scalar without being sequenced relative to each other. In this case, the side effects happen to be identical (both increment i
from its original value before the expression), but by the letter of the standard, they combine to produce UB.
The side effects are unsequenced because aside from ,
, ?:
, ||
, and &&
, operators do not define sequencing rules in terms such as C++11 §5.15/2:
If the second expression 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 expression.
The assignment operators do define a special sequencing rule, §5.17/1:
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 does not help i = i ++ + 1
because the side effect of i ++
is not part of any value computation.
Best Answer
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.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.
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.
Therefore the behavior is undefined in C++98 but well defined in C++11.
C++98
C++98 clause [expr] 5 p4
C++98 clause [expr.ass] 5.17 p1
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.
C++11
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 [intro.execution] 1.9 p15
C++11 [expr.ass] 5.17 p1
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.
(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.)
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.