- Some common cases where the evaluation order has so far been unspecified, are specified and valid with C++17. Some undefined behaviour is now instead unspecified
i = 1;
f(i++, i);-
This example was undefined, but it is now unspecified. Specifically, what is not specified is the order in which each argument to
fis evaluated relative to the others.i++might be evaluated beforei, or vice-versa. Indeed, it might evaluate a second call in a different order, despite being under the same compiler. -
However, the evaluation of each argument is required to execute completely, with all side-effects, before the execution of any other argument. So you might get
f(1, 1)(second argument evaluated first) orf(1, 2)(first argument evaluated first). But you will never getf(2, 2)or anything else of that nature. -
Code bellow was unspecified, but it will become compatible with operator precedence so that the first evaluation of
fwill come first in the stream
std::cout << f() << f() << f();-
See also postfix expressions
-
But this code bellow still has unspecified evaluation order of
g,h, andj. Note that forgetf()(g(),h(),j()), the rules state thatgetf()will be evaluated beforeg, h, j
f(g(), h(), j());- Main rules:
- Assignment expressions are evaluated from right to left. This includes compound assignments
- Operands to shift operators are evaluated from left to right
- Postfix expressions are evaluated from left to right. This includes functions calls and member selection expressions
a.b,a->b,a->*b,a(b1, b2, b3)(b1,b2,b3order is impl. def.),a @= b,a[b],a << b,a >> b
- The postfix-expression is sequenced before each expression in the expression-list and any default argument.
- The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.
#include <iostream>
struct S {
S foo(int a) {
std::cout << a;
return S{};
}
};
int main() {
S a;
a.foo(1).foo(2).foo(3); // 123
}
However, and this is important: Even when b1, b2, b3 are non-trivial expressions, each of them are completely evaluated and tied to the respective function parameter before the other ones are started to be evaluated.
In C++14, the following was unsafe:
void foo(std::unique_ptr<A>, std::unique_ptr<B>);
foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));There are four operations that happen here during the function call
new Aunique_ptr<A>constructornew Bunique_ptr<B>constructor
The ordering of these was completely unspecified, and so a perfectly valid ordering is (1), (3), (2), (4). If this ordering was selected and (3) throws, then the memory from (1) leaks - we haven't run (2) yet, which would've prevented the leak.
In C++17, the new rules prohibit interleaving. From [intro.execution]:
For each function invocation F, for every evaluation A that occurs within F and every evaluation B that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any), either A is sequenced before B or B is sequenced before A. ... In other words, function executions do not interleave with each other.
This leaves us with two valid orderings: (1), (2), (3), (4) or (3), (4), (1), (2). It is unspecified which ordering is taken, but both of these are safe. All the orderings where (1) (3) both happen before (2) and (4) are now prohibited.
#include <iostream>
struct S {
S(int b) : a(b) {}
int a;
};
int operator<<(S a, int b) {
std::cout << a.a << ' ' << b << '\n';
return 0;
}
int main() {
int i, j;
int x = S(i = 1) << (i = 2);
int y = operator<<(S(j = 1), j = 2);
}GCC output: 12 11
- Expected: 12 12
- The same problem: link
Clang output: 12 12 (as expected)