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