使用 noexcept 的函数指针的重载解析

max*_*ann 2 c++ language-lawyer

考虑到以下两个重载

template<class T_ret, class... Args>
int test(T_ret (*func)(Args...)) { return 1; }

template<typename T>
int test(const T &lambda) { return 2; }
Run Code Online (Sandbox Code Playgroud)

当使用 noexcept 说明符传递函数时,MSVC 和 gcc /clang 之间的行为存在差异。

在以下示例中:

void func() noexcept {}

int main()
{
    return test(&func);
}
Run Code Online (Sandbox Code Playgroud)

gcc 和 clang 进行第二次重载,而 MSVC 进行第一次重载 ( https://godbolt.org/z/eah3r7E8r )。所有编译器都会发现添加 noexcept 说明符的更具体的重载:

template<class T_ret, class... Args>
int test(T_ret (*func)(Args...) noexcept) { return 3; }
Run Code Online (Sandbox Code Playgroud)

在这种情况下哪种重载解决方案是正确的?

use*_*522 5

这取决于您使用的 C++ 版本。

在C++17之前noexcept不属于函数类型或函数指针类型。因此,两个函数都可以在没有任何类型转换的情况下接受参数,并且函数模板的部分排序将打破重载解析中与第一个重载的联系,第一个重载更加专门(它只接受函数指针,而第二个重载接受任何类型)。

由于 C++17noexcept是函数类型和函数指针类型的一部分,因此删除它们的转换noexcept是非恒等转换。您的第一个重载在参数中的函数指针类型上没有noexcept限定符,因此需要此转换,这使得它在转换序列的排名方面成为比第二个重载所需的恒等转换序列更糟糕的选择。

MSVC 默认使用 C++17 之前的某些标准,而最近的 GCC 默认使用 C++17,因此编译器资源管理器测试中存在差异。如果您实际设置了版本,他们就会同意。