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

Order of evaluation with function pointers in C++17

本文关键字:顺序 指针 函数 C++17      更新时间:2023-10-16

考虑C++17中的以下程序(及其在注释中的替代方案(:

#include<iostream>
void a(int) {
std::cout << "an";
}
void b(int) {
std::cout << "bn";
}
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))的结果是指向a的新指针,所以后来对f的修改应该对调用没有任何影响。我错了吗?

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

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

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

C++17 规则是,来自 [expr.call]/8:

后缀表达式在表达式列表中的每个表达式和任何默认参数之前排序。参数的初始化,包括每个关联的值计算和副作用,相对于任何其他参数的初始化都是不确定的。

(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。