这两个声明之间哪个是最好的可行运算符 == 函数?

jac*_*k X 15 c++ templates language-lawyer c++20

#include <iostream>
template<typename T>
struct A{};
struct Y{
    template<typename T>
    bool operator==(A<T>){
        std::cout<<"#1\n";
        return true;
    }
};
template<typename T>
bool operator==(T,Y){
    std::cout<<"#2\n";
    return true;
}
int main(){ 
    A<int> a;
    Y y;
    a==y;
}
Run Code Online (Sandbox Code Playgroud)

对于上面的代码片段,GCC 打印,#2而 Clang 打印#1. 结果在这里。我认为 Clang 是对的,因为它#2是表达式的非重写非成员候选者a==y。相反,合成的候选#1函数也是一个可行的函数。也就是说,重载集将由两个候选组成,如下所示:

#1'
bool operator==(A<int>,Y); [with T = int] // synthesized candidate for #1

#2'
bool operator==(A<int>,Y);[with T = A<int>]
Run Code Online (Sandbox Code Playgroud)

根据temp.func.order#3

如果重载决议中恰好有一个函数模板被重载解析通过参数颠倒顺序的重写候选 ([over.match.oper]) 考虑,则其转换模板中函数参数的顺序是颠倒的

P/A 对如下:

transformed type for #1: (A<uniqueT1>, Y) as A
original type for #2: (T, Y)  as P

transformed type for #2: (UniqueT2, Y) as A
original type for #1: (Y, A<UniqueT1>) as P
Run Code Online (Sandbox Code Playgroud)

对于上述候选者,模板函数的偏序足以确定哪个是最可行的。这是子弹over.match.best#2.5

对于某些参数 j,ICSj(F1) 是比 ICSj(F2) 更好的转换序列,或者,如果不是,

2.5 F1 和 F2 是函数模板特化,根据 [temp.func.order] 中描述的偏序规则,F1 的函数模板比​​ F2 的模板更特化,如果不是,则
[2.6 - 2.7]
2.8 F2 是重写的候选([over.match.oper])而 F​​1 不是

这意味着没有必要进入子弹 2.8。根据上述P / A对,#1至少与专门为#2#2并不至少专门的作为#1; 因此,#1'是偏序中最佳可行的候选者。所以,Clang 在这里应该是正确的。

但是,请考虑以下变体片段

#include <iostream>
template<class T>
struct A{};

class Y{};

template<class T>
bool operator==(Y,A<T>){
    std::cout<<"#1\n";
    return true;
}

template<class T>
bool operator ==(T,Y){
    std::cout<<"#2\n";
    return true;
}

int main(){
   A<int> a;
   Y y{};
   a == y;
}
Run Code Online (Sandbox Code Playgroud)

目前,Clang 和 GCC 都同意这#2是最佳可行的候选者。结果在这里。然而,在我看来,这个例子与第一个相似。仅仅是将成员候选人更改为非成员候选人。同样,重载集将由两个候选组成,如下所示:

#1''
bool operator==(A<int>,Y); [with T = int]  // synthesized candidate for #1

#2''
bool operator==(A<int>,Y); [with T = A<int>]
Run Code Online (Sandbox Code Playgroud)

在这个例子中,偏序也足以确定哪个候选是最好的。因此,#1''仍然应该是最好的。为什么 Clang 和 GCC 都认为 #2 在这个例子中是最好的?

IlC*_*ano 3

简短回答:您在示例中使用了不同的 Clang 版本,Clang 11 有正确的实现,而 Clang 10 则没有。

在我的回答中,我详细介绍了为什么 Clang 11 和 MSVC 在这两种情况下都是正确的,而 GCC 都是错误的。


[over.match.oper]#3开始,候选者包括四组:

对于二元运算符,@四组候选函数,指定成员候选函数非成员候选函数内置候选函数重写候选函数,构造如下:

在您的情况下,重写的候选者由[over.match.oper]#3.4.4确定:

对于相等运算符,对于表达式 的每个未重写候选者,重写候选者还包括一个合成候选者,其中两个参数的顺序相反y == x

在您的情况下,对于表达式,x == y感兴趣的候选者是:

  • 会员候选人: 候选人x.operator==(y)
  • 非会员候选人:候选人operator==(x, y)
  • 重写的候选者: 的非重写候选者y == x,是y.operator==(x)operator==(y, x)

让我们看看您的两个示例并确定最佳候选人。


第一个例子

对于表达式,a == y候选者是:

non-rewritten candidates:
    #2 via operator==(a, y)
rewritten candidates:
    #1 via y.operator==(a)
Run Code Online (Sandbox Code Playgroud)

为了确定更好的候选者,我们需要应用[over.match.best.general]#2,确定是否F1是更好匹配的相关规则F2如下:

(2.5) F1和是函数模板特化,并且根据 [temp.func.order] 中描述的部分排序规则,F2函数模板F1比​​模板更特化,或者,如果不是这样,F2

[...]

(2.8) F2是重写的候选者 ([over.match.oper]) 并且F1不是

如果我们采用#1to beF1#2to be ,F2我们会得到#1更好的匹配,因为(2.5)适用并且它在(2.8)之前被考虑。Clang 11+ 和最新的 MSVC#1在此处正确选择为更好的候选者(演示)。


第二个例子

对于表达式,a == y候选者是:

non-rewritten candidates:
    #2 via operator==(a, y)
rewritten candidates:
    #1 via operator==(y, a)
Run Code Online (Sandbox Code Playgroud)

应用[over.match.best.general]#2并取#1to beF1#2to be F2,类似于我们得到的第一个示例,这#1是一个更好的候选者,因为(2.5)适用并且它在(2.8)之前被考虑。与第一个示例相同,Clang 11+ 和最新的 MSVC 正确选择#1为更好的候选者(演示)。

为什么 Clang 和 GCC 都认为本例中#2 是最好的?

则不然。在演示链接中,您使用了 Clang 10,而在第一个示例中,您使用了 Clang 11。在这两种情况下,Clang 10 的结果与 GCC 相同。


在这两种情况下#1,Clang 11+ 和最新的 MSVC 都会正确选择更好的候选者。GCC 在这两种情况下都会失败,选择#2. 我的猜测是,GCC 的[over.match.best.general]#2实现是不正确的,因为它在(2.5)之前错误地考虑了(2.8),并在两种情况下都选择了未重写的候选者。