原子函数指针调用在 gcc 中编译,但在 clang 和 msvc 中不编译
Atomic function pointer call compiles in gcc, but not in clang and msvc
从原子函数指针调用函数时,例如:
#include <atomic>
#include <type_traits>
int func0(){ return 0; }
using func_type = std::add_pointer<int()>::type;
std::atomic<func_type> f = { func0 };
int main(){
f();
}
gcc 一点也不抱怨,而 clang 和 msvc 调用有问题 f()
:
- [clang]:错误:调用类型为 'std::atomic<func_type>'(又名 'atomic<int (*)()>')的对象不明确
- [msvc]:可以通过多种方式为参数列表调用“std::atomic
”类型的对象
Clang 还指定可能的调用候选者为:
operator __pointer_type() const noexcept
operator __pointer_type() const volatile noexcept
这种波动性差异似乎让 clang 和 msvc 感到困惑,但对 gcc 却没有。
当调用从 f()
更改为 f.load()()
时,代码在上述所有编译器中均有效。这更令人困惑,因为据说 load()
and operator T()
都有 const
和 const volatile
重载——如果隐式转换不起作用,我希望 load()
不会工作也一样。隐式转换(相对于成员调用)中的规则是否有所不同?
那么,gcc 接受该代码是错误的吗? clang 和 msvc 是不是错误的报错了?还有其他错误或正确的组合吗?
这主要是一个理论问题,但如果有更好的方法来获得原子函数指针,我很想知道。
Clang 和 MSVC 是正确的。
对于 class 的函数指针的每个转换函数,一个 so-called 代理调用函数 添加到重载决议中,如果选择的话首先通过此运算符重载将对象转换为函数指针,然后通过函数指针调用该函数。 [over.call.object]/2.
中对此进行了解释
但是,代理调用函数不会以任何方式翻译转换运算符的cv-qualifiers。因此,由于 std::atomic
有一个转换运算符 volatile
和一个不是,因此将有两个无法区分的代理调用函数。这些也是唯一的候选者,因为 std::atomic
没有任何实际的 operator()
,因此重载解析必须始终是模棱两可的。
标准中甚至有一个脚注提到可能会发生这种情况,请参阅 [over.call.object]/footnote.120。
通过直接调用 .load()
,volatile
-限定符将成为重载解析中的 tie-breaker,因此不会出现此问题。
With (*f)()
以函数指针类型作为参数对 (built-in) operator*
执行重载决议。通过两个转换函数有两个隐式转换序列。标准对此不是很清楚,但我认为其意图是这不会导致不明确的转换序列(这也意味着在选择时不明确的重载解析)。相反,我认为通过转换函数进行初始化的规则仅应用于 select 转换之一,这将使它明确地成为 volatile
合格的转换。
从原子函数指针调用函数时,例如:
#include <atomic>
#include <type_traits>
int func0(){ return 0; }
using func_type = std::add_pointer<int()>::type;
std::atomic<func_type> f = { func0 };
int main(){
f();
}
gcc 一点也不抱怨,而 clang 和 msvc 调用有问题 f()
:
- [clang]:错误:调用类型为 'std::atomic<func_type>'(又名 'atomic<int (*)()>')的对象不明确
- [msvc]:可以通过多种方式为参数列表调用“std::atomic
”类型的对象
Clang 还指定可能的调用候选者为:
operator __pointer_type() const noexcept
operator __pointer_type() const volatile noexcept
这种波动性差异似乎让 clang 和 msvc 感到困惑,但对 gcc 却没有。
当调用从 f()
更改为 f.load()()
时,代码在上述所有编译器中均有效。这更令人困惑,因为据说 load()
and operator T()
都有 const
和 const volatile
重载——如果隐式转换不起作用,我希望 load()
不会工作也一样。隐式转换(相对于成员调用)中的规则是否有所不同?
那么,gcc 接受该代码是错误的吗? clang 和 msvc 是不是错误的报错了?还有其他错误或正确的组合吗?
这主要是一个理论问题,但如果有更好的方法来获得原子函数指针,我很想知道。
Clang 和 MSVC 是正确的。
对于 class 的函数指针的每个转换函数,一个 so-called 代理调用函数 添加到重载决议中,如果选择的话首先通过此运算符重载将对象转换为函数指针,然后通过函数指针调用该函数。 [over.call.object]/2.
中对此进行了解释但是,代理调用函数不会以任何方式翻译转换运算符的cv-qualifiers。因此,由于 std::atomic
有一个转换运算符 volatile
和一个不是,因此将有两个无法区分的代理调用函数。这些也是唯一的候选者,因为 std::atomic
没有任何实际的 operator()
,因此重载解析必须始终是模棱两可的。
标准中甚至有一个脚注提到可能会发生这种情况,请参阅 [over.call.object]/footnote.120。
通过直接调用 .load()
,volatile
-限定符将成为重载解析中的 tie-breaker,因此不会出现此问题。
With (*f)()
以函数指针类型作为参数对 (built-in) operator*
执行重载决议。通过两个转换函数有两个隐式转换序列。标准对此不是很清楚,但我认为其意图是这不会导致不明确的转换序列(这也意味着在选择时不明确的重载解析)。相反,我认为通过转换函数进行初始化的规则仅应用于 select 转换之一,这将使它明确地成为 volatile
合格的转换。