C++17 中函数指针的求值顺序

Order of evaluation with function pointers in C++17

考虑以下 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))
}

使用 -O2 选项,Clang 9.0.0 打印 a 而 GCC 9.2 打印 b。两者都警告我关于未排序的修改和对 f 的访问。参见 godbolt.org

我的期望是这个程序具有明确定义的行为并将打印 a,因为 C++17 保证调用 (T(f)) 的左侧表达式是 排序在任何参数评估之前。因为表达式(T(f))的结果是new指向a的指针,所以后面修改f应该对调用没有任何影响.我错了吗?

如果我使用 f((f=b,0)); 而不是 (T(f))((f=b,0));,两个编译器都会给出相同的输出。在这里,我对未定义的行为方面有点不确定。这是否是未定义的行为,因为 f 在评估后仍然引用声明的函数指针,这将通过参数的评估被修改,如果是这样,为什么 会导致未定义的行为而不是调用 b?

我问了一个关于 C++17 中非静态成员函数调用求值顺序的相关问题 。我知道这样写代码是危险和不必要的,但我想更好地理解 C++ 标准的细节。

编辑:在修复了 Barry 提交的错误(参见下面他的回答)后,GCC 主干现在也打印 a。不过,Clang 和 GCC 主干仍然显示 -Wall 的误报警告。

C++17 规则是,来自 [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.

(T(f))((f=b,0));中,(T(f))(f=b, 0)的参数初始化之前排序。所有这些都是明确定义的,程序 应该 打印 "a"。也就是说,它的行为应该像:

auto __tmp = T(f);
__tmp((f=b, 0));

即使我们更改您的程序以使其有效,情况也是如此:

T{f}(f=b, 0); // two parameters now, instead of one

f=b0 表达式之间的顺序不确定,但是 T{f} 仍然排在两者之前,所以这仍然会调用 a.

归档91974.