Algunos casos comunes donde la orden de evaluación ha sido hasta ahora no especificadose especifican y son válidos con C++17
. Algunos comportamientos indefinidos ahora no están especificados.
i = 1;
f(i++, i)
no estaba definido, pero ahora no está especificado. Específicamente, lo que no se especifica es el orden en que cada argumento a f
se evalúa en relación con los demás. i++
puede ser evaluado antes i
, o viceversa. De hecho, podría evaluar una segunda llamada en un orden diferente, a pesar de estar bajo el mismo compilador.
Sin embargo, la evaluación de cada argumento es requerido para ejecutar completamente, con todos los efectos secundarios, antes de la ejecución de cualquier otro argumento. Entonces podrías obtener f(1, 1)
(segundo argumento evaluado primero) o f(1, 2)
(primer argumento evaluado primero). Pero nunca obtendrás f(2, 2)
o cualquier otra cosa de esa naturaleza.
std::cout << f() << f() << f();
no se especificó, pero será compatible con la precedencia de operadores para que la primera evaluación de f
será el primero en la transmisión (ejemplos a continuación).
f(g(), h(), j());
todavía tiene un orden de evaluación no especificado de g, h y j. Tenga en cuenta que para getf()(g(),h(),j())
las reglas establecen que getf()
será evaluado antes g, h, j
.
Tenga en cuenta también el siguiente ejemplo del texto de la propuesta:
std::string s = "but I have heard it works even if you don't believe in it"
s.replace(0, 4, "").replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");
El ejemplo viene de El lenguaje de programación C++, 4.ª edición, Stroustrup, y solía tener un comportamiento no especificado, pero con C++17 funcionará como se esperaba. Hubo problemas similares con funciones reanudables (.then( . . . )
).
Como otro ejemplo, considere lo siguiente:
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
struct Speaker{
int i =0;
Speaker(std::vector<std::string> words) :words(words) {}
std::vector<std::string> words;
std::string operator()(){
assert(words.size()>0);
if(i==words.size()) i=0;
// Pre-C++17 version:
auto word = words[i] + (i+1==words.size()?"\n":",");
++i;
return word;
// Still not possible with C++17:
// return words[i++] + (i==words.size()?"\n":",");
}
};
int main() {
auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
std::cout << spk() << spk() << spk() << spk() << spk() ;
}
Con C++14 y antes de que podamos (y obtendremos) resultados como
play
no,and,Work,All,
en lugar de
All,work,and,no,play
Tenga en cuenta que lo anterior es en efecto lo mismo que
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
Pero aún así, antes de C++17 no había garantía de que las primeras llamadas llegaran primero a la transmisión.
Referencias: Desde la propuesta aceptada:
Las expresiones de sufijo se evalúan de izquierda a derecha. Esto incluye llamadas a funciones y expresiones de selección de miembros.
Las expresiones de asignación se evalúan de derecha a izquierda. Esto incluye asignaciones compuestas.
Los operandos para desplazar a los operadores se evalúan de izquierda a derecha. En resumen, las siguientes expresiones se evalúan en el orden a, luego b, luego c, luego d:
- abdominales
- a->b
- a->*b
- a(b1, b2, b3)
- b @ = un
- a[b]
- un << b
- un >> segundo
Además, sugerimos la siguiente regla adicional: el orden de evaluación de una expresión que involucra un operador sobrecargado está determinado por el orden asociado con el operador incorporado correspondiente, no por las reglas para las llamadas a funciones.
Editar nota: Mi respuesta original mal interpretada a(b1, b2, b3)
. El orden de b1
, b2
, b3
aún no se especifica. (gracias @KABoissonneault, todos los comentaristas).
Sin embargo, (como señala @Yakk) y esto es importante: Incluso cuando b1
, b2
, b3
son expresiones no triviales, cada una de ellas se evalúa completamente y vinculado al parámetro de la función respectiva antes de que se empiecen a evaluar los demás. La norma lo dice así:
§5.2.2 – Llamada de función 5.2.2.4:
. . . La expresión postfix se secuencia antes de cada expresión en la lista de expresiones y cualquier argumento predeterminado. Cada cálculo de valor y efecto secundario asociado con la inicialización de un parámetro, y la propia inicialización, se secuencia antes de cada cálculo de valor y efecto secundario asociado con la inicialización de cualquier parámetro posterior.
Sin embargo, una de estas nuevas oraciones falta en el borrador de GitHub:
Cada cálculo de valor y efecto secundario asociado con la inicialización de un parámetro, y la propia inicialización, se secuencia antes de cada cálculo de valor y efecto secundario asociado con la inicialización de cualquier parámetro posterior.
El ejemplo es allí. Resuelve problemas de hace décadas (como lo explica Herb Sutter) con excepción de seguridad donde cosas como
f(std::unique_ptr<A> a, std::unique_ptr<B> b);
f(get_raw_a(), get_raw_a());
se filtraría si una de las llamadas get_raw_a()
lanzaría antes de que el otro puntero sin formato se vinculara a su parámetro de puntero inteligente.
Como señaló TC, el ejemplo es defectuoso ya que la construcción unique_ptr desde el puntero sin formato es explícita, lo que impide que se compile.*
También tenga en cuenta esta pregunta clásica (etiquetada Cno C++):
int x=0;
x++ + ++x;
aún no está definido.
El intercalado está prohibido en C++17
En C++ 14, lo siguiente no era seguro:
void foo(std::unique_ptr<A>, std::unique_ptr<B>);
foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
Hay cuatro operaciones que suceden aquí durante la llamada de función
new A
unique_ptr<A>
constructor
new B
unique_ptr<B>
constructor
El orden de estos no se especificó en absoluto, por lo que un orden perfectamente válido es (1), (3), (2), (4). Si se seleccionó este orden y (3) arroja, entonces la memoria de (1) se filtra; aún no hemos ejecutado (2), lo que habría evitado la fuga.
En C++17, las nuevas reglas prohíben el intercalado. Desde [intro.execution]:
Para cada invocación de función F, para cada evaluación A que ocurre dentro de F y cada evaluación B que no ocurre dentro de F pero que se evalúa en el mismo subproceso y como parte del mismo controlador de señal (si lo hay), A se secuencia antes que B o B se secuencia antes que A.
Hay una nota al pie de esa oración que dice:
En otras palabras, las ejecuciones de funciones no se intercalan entre sí.
Esto nos deja con dos órdenes válidos: (1), (2), (3), (4) o (3), (4), (1), (2). No se especifica qué pedido se toma, pero ambos son seguros. Todos los ordenamientos donde (1) (3) suceden antes de (2) y (4) ahora están prohibidos.
Encontré algunas notas sobre el orden de evaluación de expresiones:
- Pregunta rápida: ¿Por qué C++ no tiene un orden específico para evaluar los argumentos de las funciones?
En C++17 se agregaron algunas garantías de orden de evaluación que rodean a los operadores sobrecargados y reglas de argumento completo. Pero queda sin especificar qué argumento va primero. En C++17, ahora se especifica que la expresión que da a qué llamar (el código a la izquierda de ( de la llamada de función) va antes de los argumentos, y cualquier argumento que se evalúe primero se evalúa completamente antes de que comience el siguiente, y en el caso de un método de objeto el valor del objeto se evalúa antes que los argumentos del método.
- Orden de evaluación
21) Cada expresión en una lista de expresiones separadas por comas en un inicializador entre paréntesis se evalúa como si fuera una llamada de función (secuencia indeterminada)
- Expresiones ambiguas
El lenguaje C++ no garantiza el orden en que se evalúan los argumentos de una llamada de función.
En P0145R3.Refinar orden de evaluación de expresiones para C++ idiomático He encontrado:
El cálculo del valor y el efecto secundario asociado de la expresión de sufijo se secuencian antes que los de las expresiones en la lista de expresiones. Las inicializaciones de los parámetros declarados son indeterminadamente secuenciado sin intercalado.
Pero no lo encontré en estándar, en cambio en estándar encontré:
6.8.1.8 Ejecución secuencial [intro.execution]
Se dice que una expresión X está secuenciada antes que una expresión Y si cada cálculo de valor y cada efecto secundario asociado con la expresión X está secuenciado antes de cada cálculo de valor y cada efecto secundario asociado con la expresión Y.
6.8.1.9 Ejecución secuencial [intro.execution]
Cada cálculo de valor y efecto secundario asociado con una expresión completa se secuencia antes de cada cálculo de valor y efecto secundario asociado con la siguiente expresión completa que se va a evaluar.
7.6.19.1 Operador coma [expr.comma]
Un par de expresiones separadas por una coma se evalúa de izquierda a derecha;…
Entonces, comparé el comportamiento de acuerdo en tres compiladores para los estándares 14 y 17. El código explorado es:
#include <iostream>
struct A
{
A& addInt(int i)
{
std::cout << "add int: " << i << "\n";
return *this;
}
A& addFloat(float i)
{
std::cout << "add float: " << i << "\n";
return *this;
}
};
int computeInt()
{
std::cout << "compute int\n";
return 0;
}
float computeFloat()
{
std::cout << "compute float\n";
return 1.0f;
}
void compute(float, int)
{
std::cout << "compute\n";
}
int main()
{
A a;
a.addFloat(computeFloat()).addInt(computeInt());
std::cout << "Function call:\n";
compute(computeFloat(), computeInt());
}
Resultados (cuanto más consistente es el sonido metálico):
<style type="text/css">
.tg {
border-collapse: collapse;
border-spacing: 0;
border-color: #aaa;
}
.tg td {
font-family: Arial, sans-serif;
font-size: 14px;
padding: 10px 5px;
border-style: solid;
border-width: 1px;
overflow: hidden;
word-break: normal;
border-color: #aaa;
color: #333;
background-color: #fff;
}
.tg th {
font-family: Arial, sans-serif;
font-size: 14px;
font-weight: normal;
padding: 10px 5px;
border-style: solid;
border-width: 1px;
overflow: hidden;
word-break: normal;
border-color: #aaa;
color: #fff;
background-color: #f38630;
}
.tg .tg-0pky {
border-color: inherit;
text-align: left;
vertical-align: top
}
.tg .tg-fymr {
font-weight: bold;
border-color: inherit;
text-align: left;
vertical-align: top
}
</style>
<table class="tg">
<tr>
<th class="tg-0pky"></th>
<th class="tg-fymr">C++14</th>
<th class="tg-fymr">C++17</th>
</tr>
<tr>
<td class="tg-fymr"><br>gcc 9.0.1<br></td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
</tr>
<tr>
<td class="tg-fymr">clang 9</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
</tr>
<tr>
<td class="tg-fymr">msvs 2017</td>
<td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
</tr>
</table>
Relacionado con el orden de evaluación de la instrucción de asignación en C++ y ¿Este código de la sección 36.3.6 de la cuarta edición de “El lenguaje de programación C++” tiene un comportamiento bien definido? ambos cubiertos por el papel. El primero podría ser un buen ejemplo adicional en su respuesta a continuación.
– Shafik Yaghmour
15 mayo 2017 a las 17:57
También algo relacionado: orden de evaluación c ++ 17 con funciones de sobrecarga del operador.
– dfrib
29 de septiembre de 2017 a las 6:54