当混合成员和非成员二元运算符时,clang 是否错误地报告了歧义?

Luc*_*lle 6 c++ operator-overloading clang language-lawyer 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

Qui*_*mby 2

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

\n

将成员转换为独立函数

\n

首先,以下代码片段不等于您在调用时发布的代码S<int>() | 42;

\n
template <typename T>\nstruct S\n{\n\n};\ntemplate <typename T,typename U>\nvoid operator|(S<T>,U)\n{}\n\ntemplate <typename T>\nvoid operator|(S<T>,int) {} \n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

过载分辨率

\n

鉴于您发布的代码。

\n

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

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

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

\n
\n

[over.match.funcs.4]

\n

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

\n
    \n
  • (4.1)\n\xe2\x80\x9cl 对 cv X\xe2\x80\x9d 的值引用,用于不带引用限定符或使用 & 引用限定符声明的函数
  • \n
  • (4.2)\n\xe2\x80\x9cr 对使用 && 引用限定符声明的函数的 cv X\xe2\x80\x9d 的引用
  • \n
\n

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

\n
\n

这导致:

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

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

\n
\n

[over.match.funcs.5] [强调我的]

\n

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

\n
\n

由于一些其他规则,U被推导为int、 不是const int&int&S<T>也可以很容易地推断出 和S<int>S<int>可复制的。

\n

因此,两位候选人仍然有效。此外,两者都不比另一个更专业。我不会一步一步地完成这个过程,因为我无法知道如果所有编译器都没有得到这里的规则,谁能责怪我。但它是模棱两可的,其原因foo(42)

\n
void foo(int){}\nvoid foo(const int&){}\n
Run Code Online (Sandbox Code Playgroud)\n

即,只要引用可以绑定值,复制和引用之间就没有优先权。S<int>&即使由于上述规则,在我们的例子中也是如此。该决议仅针对两个参数而不是一个参数完成。

\n