En la sección Operaciones similares a STL del lenguaje de programación C ++ de Bjarne Stroustrup, la cuarta edición, se utiliza el siguiente código como ejemplo de encadenamiento :36.3.6
void f2()
{
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, "" );
assert( s == "I have heard it works only if you believe in it" ) ;
}
La aserción falla en gcc
( verlo en vivo ) y Visual Studio
( verlo en vivo ), pero no falla cuando se usa Clang ( verlo en vivo ).
¿Por qué obtengo resultados diferentes? ¿Alguno de estos compiladores está evaluando incorrectamente la expresión de encadenamiento o este código muestra alguna forma de comportamiento no especificado o indefinido ?
c++
c++11
language-lawyer
operator-precedence
unspecified-behavior
Shafik Yaghmour
fuente
fuente
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );
cout << a << b << c
≡operator<<(operator<<(operator<<(cout, a), b), c)
es solo un poco menos feo.Respuestas:
El código exhibe un comportamiento no especificado debido a un orden no especificado de evaluación de sub-expresiones, aunque no invoca un comportamiento indefinido, ya que todos los efectos secundarios se realizan dentro de funciones, lo que introduce una relación de secuenciación entre los efectos secundarios en este caso.
Este ejemplo se menciona en la propuesta N4228: Refining Expression Evaluation Order for Idiomatic C ++, que dice lo siguiente sobre el código de la pregunta:
Detalles
Puede ser obvio para muchos que los argumentos de las funciones tienen un orden de evaluación no especificado, pero probablemente no sea tan obvio cómo este comportamiento interactúa con las llamadas de funciones encadenadas. No fue obvio para mí cuando analicé este caso por primera vez y aparentemente no para todos los revisores expertos. .
A primera vista, puede parecer que, dado que cada
replace
debe evaluarse de izquierda a derecha, los grupos de argumentos de función correspondientes también deben evaluarse como grupos de izquierda a derecha.Esto es incorrecto, los argumentos de función tienen un orden de evaluación no especificado, aunque el encadenamiento de llamadas de función introduce un orden de evaluación de izquierda a derecha para cada llamada de función, los argumentos de cada llamada de función solo se secuencian antes con respecto a la llamada de función miembro de la que forman parte de. En particular, esto afecta las siguientes convocatorias:
y:
que están secuenciados indeterminadamente con respecto a:
las dos
find
llamadas podrían evaluarse antes o después dereplace
, lo cual es importante, ya que tiene un efecto secundario des
una manera que alteraría el resultado defind
, cambia la longitud des
. Entonces, dependiendo de cuándoreplace
se evalúe en relación con los dosfind
llamadas, el resultado será diferente.Si miramos la expresión de encadenamiento y examinamos el orden de evaluación de algunas de las sub-expresiones:
y:
Tenga en cuenta que estamos ignorando el hecho de que
4
y7
se puede dividir en más sub-expresiones. Entonces:A
se secuencia antes deB
que se secuencia antes deC
que se secuencia antesD
1
a9
tienen una secuencia indeterminada con respecto a otras sub-expresiones con algunas de las excepciones que se enumeran a continuación1
a3
se secuencian antesB
4
a6
se secuencian antesC
7
a9
se secuencian antesD
La clave de este problema es que:
4
a9
están secuenciados indeterminadamente con respecto aB
El orden potencial de elección de la evaluación para
4
y7
con respecto aB
explica la diferencia en los resultados entreclang
ygcc
al evaluarf2()
. En mis pruebasclang
evalúaB
antes de evaluar4
y7
whilegcc
evalúa después. Podemos utilizar el siguiente programa de prueba para demostrar lo que está sucediendo en cada caso:Resultado para
gcc
( verlo en vivo )Resultado para
clang
( verlo en vivo ):Resultado para
Visual Studio
( verlo en vivo ):Detalles del estándar
Sabemos que, a menos que se especifique, las evaluaciones de las subexpresiones no están secuenciadas, esto es del borrador de la sección estándar C ++ 11
1.9
Ejecución del programa que dice:y sabemos que una llamada de función introduce una relación secuenciada antes de las llamadas de función expresión y argumentos de postfijo con respecto al cuerpo de la función, de la sección
1.9
:También sabemos que el acceso de los miembros de la clase y, por lo tanto, el encadenamiento se evaluará de izquierda a derecha, desde la sección
5.2.5
Acceso de miembros de la clase que dice:Tenga en cuenta que en el caso de que la expresión id termine siendo una función miembro no estática, no especifica el orden de evaluación de la lista de expresiones dentro de la
()
ya que es una subexpresión separada. La gramática relevante de las5.2
expresiones Postfix :C ++ 17 cambios
La propuesta p0145r3: Refining Expression Evaluation Order for Idiomatic C ++ realizó varios cambios. Incluyendo cambios que le dan al código un comportamiento bien especificado al fortalecer el orden de las reglas de evaluación para expresiones-postfijo y su lista de expresiones .
[expr.call] p5 dice:
fuente
"even"
,"don't"
y las diversas instancias de nos
están secuenciadas entre sí.foo().func( bar() )
, podría llamarfoo()
antes o después de llamarbar()
. La expresión-postfijo esfoo().func
. Los argumentos y la expresión-postfijo están secuenciados antes del cuerpo defunc()
, pero no están secuenciados entre sí.Esto tiene la intención de agregar información sobre el tema con respecto a C ++ 17. La propuesta ( Refining Expression Evaluation Order for Idiomatic C ++ Revision 2 ) para
C++17
abordar el problema citando el código anterior fue como muestra.Como se sugirió, agregué información relevante de la propuesta y cito (destaca la mía):
El documento sugirió cambiar la
C++17
regla previa sobre el orden de evaluación de las expresiones, que fue influenciada porC
y ha existido durante más de tres décadas. Propuso que el lenguaje debería garantizar modismos contemporáneos o arriesgarse a "trampas y fuentes de errores oscuros y difíciles de encontrar". , como lo que sucedió con el espécimen de código anterior.La propuesta
C++17
es exigir que cada expresión tenga un orden de evaluación bien definido :El código anterior se compila con éxito usando
GCC 7.1.1
yClang 4.0.0
.fuente