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

Is clang wrongfully reporting ambiguity when mixing member and non-member binary operators?

考虑下面的代码,它混合了一个成员和一个非成员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 中的错误吗?

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

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

将成员转换为独立函数

首先,以下代码段不等于您发布的代码 w.r.t S<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,从而使第二个模板更加专业,因此被选中。正如您所观察到的,所有编译器都同意这一点。

过载分辨率

根据您发布的代码。

要进行重载解析,首先要启动名称查找。这包括在当前上下文中查找所有名为 operator| 的符号,在 S<int> 的所有成员中,以及与此处无关的 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]

For implicit object member functions, the type of the implicit object parameter is

  • (4.1) “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
  • (4.2) “rvalue reference to cv X” for functions declared with the && ref-qualifier

where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.

这导致:

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

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

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

During overload resolution, the implied object argument is indistinguishable from other arguments. The implicit object parameter, however, retains its identity since no user-defined conversions can be applied to achieve a type match with it. For implicit object member functions declared without a ref-qualifier, even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.

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

所以两位候选人仍然有效。此外,两者都不比另一个更专业。我不会逐步完成整个过程,因为我无法如果所有编译器都没有得到这里的规则,谁能怪我。但由于 foo(42)

的原因,它是模棱两可的
void foo(int){}
void foo(const int&){}

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