clang在混合成员和非成员二进制运算符时是否错误地报告了歧义



考虑以下代码,它混合了成员和非成员operator|

template <typename T>
struct S
{
template <typename U>
void operator|(U)
{}
};
template <typename T>
void operator|(S<T>,int) {} 
int main()
{
S<int>() | 42;
}

在Clang中,这段代码无法编译,说明对operator|的调用不明确,而gcc和msvc可以编译它。我希望选择非成员重载,因为它更专业。标准规定的行为是什么?这是克兰的一只虫子吗?

(请注意,将运算符模板移动到结构外部可以解决歧义。)

我相信clang将调用标记为不明确是正确的。

将成员转换为独立函数

首先,下面的代码段不等于您发布的w.r.tS<int>() | 42;调用的代码段。

template <typename T>
struct S
{
};
template <typename T,typename U>
void operator|(S<T>,U)
{}
template <typename T>
void operator|(S<T>,int) {} 

在这种情况下,现在的非成员实现必须推导出TU,从而使第二个模板更加专业化,从而进行选择。正如您所观察到的,所有编译器都同意这一点。

过载分辨率

给定您发布的代码。

要进行过载解析,首先启动名称查找。这包括在当前上下文中查找S<int>的所有成员中名为operator|的所有符号,以及ADL规则给出的名称空间,这些名称空间在这里不相关。至关重要的是,T在这个阶段得到了解决,在过载解决之前,它必须得到解决。因此,找到的符号就是

  • template <typename T> void operator|(S<T>,int)
  • template <typename U> void S<int>::operator|(U)

为了选择更好的候选者,所有成员函数都被视为具有特殊*this参数的非成员。S<int>&

[over.match.funcs.4]

对于隐式对象成员函数,隐式对象参数的类型为

  • (4.1)"对cv X的左值引用",用于未使用ref限定符或使用&ref限定符
  • (4.2)对于用&amp;ref限定符

其中X是函数是其成员的类,cv是成员函数声明上的cv限定。

这导致:

  • template <typename T> void operator|(S<T>,int)
  • template <typename U> void operator|(S<int>&,U)

考虑到这一点,人们可能会认为,因为第二个函数无法绑定调用中使用的右值,所以必须选择第一个函数。不,有一条特殊的规则涵盖了这一点:

[over.match.funcs.5][Emphasis mine]

在重载解析过程中,隐含对象参数与其他参数无法区分。但是,隐式对象参数保留了它的标识,因为不能应用用户定义的转换来实现与它的类型匹配。对于在没有ref限定符的情况下声明的隐式对象成员函数,即使隐式对象参数不是const限定的,也可以将右值绑定到该参数,只要该参数在所有其他方面都可以转换为隐式对象参数的类型

由于一些其他规则,U被推导为int,而不是const int&int&。CCD_ 19也可以很容易地推导为CCD_ 20和CCD_ 21是可复制的。

因此,两位候选人仍然有效。此外,两者都不比另一个更专业。我不会一步一步地完成这个过程,因为我不能如果所有的编译器都没有正确地理解规则,谁会责怪我。但它是模糊的,原因与foo(42)用于的原因完全相同

void foo(int){}
void foo(const int&){}

也就是说,复制和引用之间没有偏好,只要引用可以绑定值。由于上述规则,即使对于S<int>&,在我们的情况下也是如此。这项决议只是针对两个论点而不是一个论点。

最新更新