Прежде чем вы начнете кричать неопределенное поведение, это явно указано в N4659 (C ++ 17)
i = i++ + 1; // the value of i is incremented
Еще в N3337 (C ++ 11)
i = i++ + 1; // the behavior is undefined
Что изменилось?
Из того, что я могу собрать, из [N4659 basic.exec]
За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными. [...] Вычисления значений операндов оператора секвенируются до вычисления значения результата оператора. Если побочный эффект в области памяти не секвенирован относительно другого побочного эффекта в той же области памяти или вычисления значения с использованием значения любого объекта в той же области памяти, и они не являются потенциально одновременными, поведение не определено.
Где значение определено в [N4659 basic.type]
Для тривиально копируемых типов представление значения представляет собой набор битов в представлении объекта, который определяет значение , которое является одним дискретным элементом набора значений, определенного реализацией.
За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными. [...] Вычисления значений операндов оператора секвенируются до вычисления значения результата оператора. Если побочный эффект на скалярный объект не секвенирован относительно другого побочного эффекта на тот же скалярный объект или вычисления значения с использованием значения того же скалярного объекта, поведение не определено.
Аналогично, значение определяется в [N3337 basic.type]
Для тривиально копируемых типов представление значения представляет собой набор битов в представлении объекта, который определяет значение , которое является одним дискретным элементом определенного реализацией набора значений.
Они идентичны, за исключением упоминания параллелизма, который не имеет значения, и с использованием области памяти вместо скалярного объекта , где
Арифметические типы, типы перечисления, типы указателей, указатели на типы элементов
std::nullptr_t
и cv-квалифицированные версии этих типов вместе называются скалярными типами.
Что не влияет на пример.
Оператор присваивания (=) и составные операторы присваивания все группы справа налево. Все они требуют изменяемого lvalue в качестве своего левого операнда и возвращают lvalue, ссылаясь на левый операнд. Результатом во всех случаях является битовое поле, если левый операнд является битовым полем. Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и до вычисления значения выражения присваивания. Правый операнд упорядочен перед левым операндом.
Оператор присваивания (=) и составные операторы присваивания все группы справа налево. Все они требуют изменяемого lvalue в качестве своего левого операнда и возвращают lvalue, ссылаясь на левый операнд. Результатом во всех случаях является битовое поле, если левый операнд является битовым полем. Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и до вычисления значения выражения присваивания.
Единственное отличие состоит в том, что последнее предложение отсутствует в N3337.
Последнее предложение, однако, не должно иметь никакого значения, так как левый операнд не i
является ни «другим побочным эффектом», ни «использованием значения того же скалярного объекта», поскольку id-выражение является lvalue.
i = i++ + 1;
.