Antes de comenzar a gritar comportamiento indefinido, esto se enumera explícitamente en N4659 (C ++ 17)
i = i++ + 1; // the value of i is incremented
Sin embargo, en N3337 (C ++ 11)
i = i++ + 1; // the behavior is undefined
¿Qué cambió?
De lo que puedo reunir, de [N4659 basic.exec]
Excepto donde se indique, las evaluaciones de operandos de operadores individuales y de subexpresiones de expresiones individuales no tienen secuencia. [...] Los cálculos del valor de los operandos de un operador se ordenan antes del cálculo del valor del resultado del operador. Si un efecto secundario en una ubicación de memoria no está secuenciado en relación con otro efecto secundario en la misma ubicación de memoria o un cálculo de valor utilizando el valor de cualquier objeto en la misma ubicación de memoria, y no son potencialmente concurrentes, el comportamiento es indefinido.
Donde el valor se define en [N4659 basic.type]
Para los tipos que se pueden copiar trivialmente, la representación del valor es un conjunto de bits en la representación del objeto que determina un valor , que es un elemento discreto de un conjunto de valores definidos por la implementación
Desde [N3337 basic.exec]
Excepto donde se indique, las evaluaciones de operandos de operadores individuales y de subexpresiones de expresiones individuales no tienen secuencia. [...] Los cálculos del valor de los operandos de un operador se secuencian antes del cálculo del valor del resultado del operador. Si un efecto secundario sobre un objeto escalar no está secuenciado en relación con otro efecto secundario sobre el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento es indefinido.
Del mismo modo, el valor se define en [N3337 basic.type]
Para los tipos que se pueden copiar trivialmente, la representación del valor es un conjunto de bits en la representación del objeto que determina un valor , que es un elemento discreto de un conjunto de valores definidos por la implementación.
Son idénticos, excepto la mención de concurrencia que no importa, y con el uso de la ubicación de la memoria en lugar del objeto escalar , donde
Los tipos aritméticos, los tipos de enumeración, los tipos de puntero, los tipos de puntero a miembro
std::nullptr_t
y las versiones calificadas por cv de estos tipos se denominan colectivamente tipos escalares.
Lo que no afecta el ejemplo.
Desde [N4659 expr.ass]
El operador de asignación (=) y los operadores de asignación compuesta se agrupan de derecha a izquierda. Todos requieren un valor l modificable como su operando izquierdo y devuelven un valor l referido al operando izquierdo. El resultado en todos los casos es un campo de bits si el operando izquierdo es un campo de bits. En todos los casos, la asignación se secuencia después del cálculo del valor de los operandos derecho e izquierdo, y antes del cálculo del valor de la expresión de asignación. El operando derecho se secuencia antes que el operando izquierdo.
El operador de asignación (=) y los operadores de asignación compuesta se agrupan de derecha a izquierda. Todos requieren un valor l modificable como su operando izquierdo y devuelven un valor l referido al operando izquierdo. El resultado en todos los casos es un campo de bits si el operando izquierdo es un campo de bits. En todos los casos, la asignación se secuencia después del cálculo del valor de los operandos derecho e izquierdo, y antes del cálculo del valor de la expresión de asignación.
La única diferencia es que la última oración está ausente en N3337.
Sin embargo, la última oración no debería tener importancia ya que el operando izquierdo i
no es "otro efecto secundario" ni "usar el valor del mismo objeto escalar" ya que la expresión id es un valor l.
i = i++ + 1;
.