29

I've seen the other similar questions and read the defect about it. But I still don't get it. Why is i = ++i + 1 well-defined in C++11 when i = i++ + 1 is not? How does the standard make this well defined?

By my working out, I have the following sequenced before graph (where an arrow represents the sequenced before relationship and everything is a value computation unless otherwise specified):

i = ++i + 1
     ^
     |
assignment (side effect on i)
 ^      ^
 |      |
☆i   ++i + 1
     ||    ^
    i+=1   |
     ^     1
     |
★assignment (side effect on i)
  ^      ^
  |      |
  i      1

I've marked a side effect on i with a black star and value computation of i with a white star. These appear to be unsequenced with respect to each other (according to my logic). And the standard says:

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.

The explanation in the defect report didn't help me understand. What does the lvalue-to-rvalue conversion have to do with anything? What have I gotten wrong?

Community
  • 1
  • 1
Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324

3 Answers3

22

... or a value computation using the value of the same scalar object ...

The important part is bolded here. The left hand side does not use the value of i for the value computation. What is being computed is a glvalue. Only afterwards (sequenced after), the value of the object is touched and replaced.

Unfortunately this is a very subtle point :)

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Interesting. Do you have any standard quotes? How do we know the left operand doesn't use the value of `i`? – Joseph Mansfield Dec 22 '12 at 18:55
  • 1
    @sftrabbit Not a quote from the standard, but anyway... If it did, then `int i; i = 5;` would have undefined behaviour :) –  Dec 22 '12 at 18:57
  • Because on the left side we don't read the value (reading the value would be an lvalue to rvalue conversion) and we don't write it before the assignment. It just stays a reference to the object `i`, which it must because we need to modify it. – Johannes Schaub - litb Dec 22 '12 at 18:58
  • @hvd No, that would be fine. There is no side effect on `i` in the right operand. – Joseph Mansfield Dec 22 '12 at 18:59
  • The spec says "Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (4.1), array-to-pointer (4.2), or function-to-pointer (4.3) standard conversions are applied to convert the expression to a prvalue.". Assigment isn't one of the operators that expect an rvalue on the left side. Quite the opposite :) – Johannes Schaub - litb Dec 22 '12 at 19:00
  • @JohannesSchaub-litb Okay, I'm almost there. Because assignment takes an lvalue as its left operand, there is no lvalue-to-rvalue conversion involved so its value is not used. Where does the spec say that "lvalue-to-rvalue conversion" denotes "using the value"? – Joseph Mansfield Dec 22 '12 at 19:02
  • @sftrabbit You were concerned that the assignment operator might read the value of its left operand. I used that example because the behaviour is not defined when reading an indeterminate int, even if it is only read to discard it. –  Dec 22 '12 at 19:02
  • 4.1p2: "Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.". – Johannes Schaub - litb Dec 22 '12 at 19:03
  • @hvd I see. Interesting example. – Joseph Mansfield Dec 22 '12 at 19:03
  • @JohannesSchaub-litb Ah, I think I get it now. I just assumed that value computation of operands involves using the value of the object. But now I see that it only accesses the value of the object under certain circumstances. One of those circumstances is in some cases of lvalue-to-rvalue conversion. Right? – Joseph Mansfield Dec 22 '12 at 19:09
  • 2
    @sftrabbit yes. "value" in "value computation" is used in a different meaning that what "value" actually means. I think of it more like "result computation". Note that the Standard says at 1.9p12 "... value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation)". – Johannes Schaub - litb Dec 22 '12 at 19:13
  • Thanks! I've got to say, "value computation" is badly defined - in fact, it's not formally defined at all. Could do with being neatened up. – Joseph Mansfield Dec 22 '12 at 19:20
  • 1
    What if one replaces `=` by `+=` to give `i += ++i + 1`? The standard seems to contradict itself, since the LHS of `+=` is still an lvalue, so its evaluation does not have conflicts with the RHS; in order to do the addition in `+=` the current value of `i` must be obtained, but since the value computation of an operator is sequenced _after_ that of its operands, there is no problem. However the standard also says that `E += F` is equivalent to `E = E + F` (except that (the lvalue) `E` is evaluated only once which makes no difference here), but `i = i + (++i + 1)` has undefined behaviour! – Marc van Leeuwen Jun 12 '14 at 15:31
16

Well, the key moment here is that the result of ++i is an lvalue. And in order to participate in binary + that lvalue has to be converted to an rvalue by lvalue-to-rvalue conversion. Lvalue-to-rvalue conversion is basically an act of reading the variable i from memory.

That means that the value of ++i is supposed to be obtained as if it was read directly from i. That means that conceptually the new (incremented) value of i must be ready (must be physically stored in i) before the evaluation of binary + begins.

This extra sequencing is what inadvertently made the behavior defined in this case.

In C++03 there was no strict requirement to obtain the value of ++i directly from i. The new value could have been predicted/precalulated as i + 1 and used as an operand of binary + even before it was physically stored in the the actual i. Although it can be reasonably claimed that the requirement was implicitly there even in C++03 (and the fact that C++03 did not recognize its existence was a defect of C++03)


In case of i++, the result is an rvalue. Since it is already an rvalue, there's no lvalue-to-rvalue conversion involved and there's absolutely no requirement to store it in i before we start evaluating the binary +.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • But that still doesn't make it sequenced with respect to the value computation of `i`, does it? – Joseph Mansfield Dec 22 '12 at 18:54
  • 1
    @sftrabbit: I'm not sure what you mean. You see, the result of `++i` has two properties: 1) it is an lvalue, 2) numerically, it is the new (incremented) value of `i`. The only way to make these two requirements to coexist is to require that the modification of `i` by `++i` is sequenced *before* any further use of the result of `++i` in a surround expression. In fact, this logic applied to C++03 as well, but the intent of C++03 was to keep this behavior undefined, so C++03 refused to accept this logic. – AnT stands with Russia Dec 22 '12 at 19:02
  • 1
    It was expected that further changes to the standard will keep it as UB, i.e. something else in the standard was expected to be redesigned in order to preserve the "UB freedom" in `i = ++i + 1` and at the same time remove the above issue from the `++i` specification. However, in the end things played out differently and the changes made in C++11 actually supported the above logic instead of preventing it. So, we ended up with defined behavior in `i = ++i + 1`, even though no one was trying to legalize this code deliberately. – AnT stands with Russia Dec 22 '12 at 19:05
  • He expected that the second statement in "int i; i;" will do "a value computation using the value of the same scalar object", and so we would have an unsequenced value computation and assignment of "i" in his example expression. But since in "i;", just as in his example on the left side, there is no accessing of the value of "i", there is no conflict. – Johannes Schaub - litb Dec 22 '12 at 19:07
  • @AndreyT Thanks for your answer! It helped too, but the other answer caught the main thing that I was misunderstanding. – Joseph Mansfield Dec 22 '12 at 19:14
  • 1
    does the standard explicitly state that for `++i` (used in an rvalue context) that the increment is *sequenced-before* the lvalue-to-rvalue conversion? Where is the exact wording change that you have paraphrased as "In C++03 there was no strict requirement" – M.M Aug 20 '14 at 10:23
4

Long since asking this question, I have written an article on visualising C++ evaluation sequencing with graphs. This question is the identical to the case of i = ++i, which has the following sequenced-before graph:

Sequenced-before graph for i = ++i

The red nodes represent side effects on i. The blue node represents an evaluation that uses the value of i. The reason this expression is well-defined is because there are none of these nodes unsequenced with each other. The use of i on the left of the assigment doesn't use the value of i, so is not a problem.

The main part that I was missing was what "uses the value" means. An operator uses the value of its operand if it expects a prvalue expression for that operand. When giving the name of an object, which is an lvalue, the lvalue has to undergo lvalue-to-rvalue conversion, which can be seen as "reading the value of the object". Assignment only requires an lvalue for its left operand, which means that it doesn't use its value.

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324