What are the implications of the voted in C++17 evaluation order guarantees (P0145) on typical C++ code?
What does it change about things like the following?
I've found some notes about expression evaluation order:
Some order of evaluation guarantees surrounding overloaded operators and complete-argument rules where added in C++17. But it remains that which argument goes first is left unspecified. In C++17, it is now specified that the expression giving what to call (the code on the left of the ( of the function call) goes before the arguments, and whichever argument is evaluated first is evaluated fully before the next one is started, and in the case of an object method the value of the object is evaluated before the arguments to the method are.
21) Every expression in a comma-separated list of expressions in a parenthesized initializer is evaluated as if for a function call (indeterminately-sequenced)
The C++ language does not guarantee the order in which arguments to a function call are evaluated.
In P0145R3.Refining Expression Evaluation Order for Idiomatic C++ I've found:
The value computation and associated side-effect of the postfix-expression are sequenced before those of the expressions in the expression-list. The initializations of the declared parameters are indeterminately sequenced with no interleaving.
But I didn't find it in standard, instead in standard I've found:
6.8.1.8 Sequential execution [intro.execution] An expression X is said to be sequenced before an expression Y if every value computation and every side effect associated with the expression X is sequenced before every value computation and every side effect associated with the expression Y.
6.8.1.9 Sequential execution [intro.execution] Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated.
7.6.19.1 Comma operator [expr.comma] A pair of expressions separated by a comma is evaluated left-to-right;...
So, I compared according behavior in three compilers for 14 and 17 standards. The explored code is:
#include
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());
}
Results (the more consistent is clang):
C++14
C++17
gcc 9.0.1
compute float
add float: 1
compute int
add int: 0
Function call:
compute int
compute float
compute
compute float
add float: 1
compute int
add int: 0
Function call:
compute int
compute float
compute
clang 9
compute float
add float: 1
compute int
add int: 0
Function call:
compute float
compute int
compute
compute float
add float: 1
compute int
add int: 0
Function call:
compute float
compute int
compute
msvs 2017
compute int
compute float
add float: 1
add int: 0
Function call:
compute int
compute float
compute
compute float
add float: 1
compute int
add int: 0
Function call:
compute int
compute float
compute