What made i = i++ + 1; legal in C++17?

Before you start yelling undefined behaviour, this is explicitly listed in N4659 (C++17)

  i = i++ + 1;        // the value of i is incremented

Yet in N3337 (C++11)

  i = i++ + 1;        // the behavior is undefined

What changed?

From what I can gather, from [N4659 basic.exec]

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. […] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a memory location is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location, and they are not potentially concurrent, the behavior is undefined.

Where value is defined at [N4659 basic.type]

For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values

From [N3337 basic.exec]

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. […] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. 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.

Likewise, value is defined at [N3337 basic.type]

For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values.

They are identical except mention of concurrency which doesn’t matter, and with the usage of memory location instead of scalar object, where

Arithmetic types, enumeration types, pointer types, pointer to member types, std::nullptr_t, and cv-qualified versions of these types are collectively called scalar types.

Which doesn’t affect the example.

From [N4659 expr.ass]

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. 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. The right operand is sequenced before the left operand.

From [N3337 expr.ass]

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. 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.

The only difference being the last sentence being absent in N3337.

The last sentence however, shouldn’t have any importance as the left operand i is neither “another side effect” nor “using the value of the same scalar object” as the id-expression is a lvalue.

4 Answers
4

Leave a Comment