Consider the following program (and its alternative in the comment) in C++17:
#include<iostream>
void a(int) {
std::cout << "a\n";
}
void b(int) {
std::cout << "b\n";
}
int main() {
using T = void(*)(int);
T f = a;
(T(f))((f=b,0)); // alternatively: f((f=b,0))
}
With -O2
option, Clang 9.0.0 prints a
and GCC 9.2 prints b
. Both warn me about unsequenced modification and access to f
. See godbolt.org.
My expectation was that this is program has well-defined behavior and will print a
, because C++17 guarantees that the left-hand expression of the call (T(f))
is sequenced before any evaluation of the arguments. Because the result of the expression (T(f))
is a new pointer to a
, the later modification of f
should have no impact on the call at all. Am I wrong?
Both compilers give the same output if I use f((f=b,0));
instead of (T(f))((f=b,0));
. Here I am slightly unsure about the undefined behavior aspect. Would this be undefined behavior because f
still refers to the declared function pointer after evaluation, which will have been modified by the evaluation of the arguments and if so, why exactly would that cause undefined behavior rather than calling b
?
I have asked a related question with regards to order of evaluation of non-static member function calls in C++17 here. I am aware that writing code like this is dangerous and unnecessary, but I want to understand the details of the C++ standard better.
Edit: GCC trunk now also prints a
after the bug filed by Barry (see his answer below) has been fixed. Both Clang and GCC trunk do still show false-positive warnings with -Wall
, though.
The C++17 rule is, from [expr.call]/8:
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.
In (T(f))((f=b,0));
, (T(f))
is sequenced before the initialization of the parameter from (f=b, 0)
. All of this is well-defined and the program should print "a". That is, it should behave just like:
auto __tmp = T(f);
__tmp((f=b, 0));
The same is true even if we change your program such that this were valid:
T{f}(f=b, 0); // two parameters now, instead of one
The f=b
and 0
expressions are indeterminately sequenced with each other, but T{f}
is still sequenced before both, so this would still invoke a
.
Filed 91974.