提问者:小点点

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


#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;
}

对于上面的代码段,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>]

根据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

对于上述候选,模板函数的部分排序足以确定哪个是最佳可行的。哪个是子弹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]),而F1不是

这意味着没有必要进入第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;
}

此时,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>]

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


共1个答案

匿名用户

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

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

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

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

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

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

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

  • 成员候选:x. Operers==(y)
  • 的候选者
  • 非成员候选者:运算符==(x, y)
  • 的候选者
  • 重写候选:y==x的非重写候选,即y. Operers==(x)Operers==(y,x)

让我们看看你的两个例子,并确定最佳候选人。

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

non-rewritten candidates:
    #2 via operator==(a, y)
rewritten candidates:
    #1 via y.operator==(a)

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

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

[...]

(2.8)F2是重写的候选([over. match.oper]),而F1不是

如果我们将#1作为F1,将#2作为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)

应用[over.match.best. General]#2并将#1作为F1,将#2作为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),并在这两种情况下选择了未重写的候选者。