【问题标题】:Which is the best viable operator == function between these two declarations?这两个声明之间最好的操作符 == 函数是哪个?
【发布时间】:2021-06-13 09:23:30
【问题描述】:
#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;
}

对于上面的 sn-p,GCC 打印 #2 而 Clang 打印 #1。结果是here。我认为 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 在这里应该是正确的。

但是,请考虑以下变体 sn-p

#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 是最可行的候选者。结果是here。但是,在我看来,此示例与第一个示例相似。只是,它将会员候选人更改为非会员候选人。同样,重载集将由两个候选者组成,如下所示:

#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 在这个例子中是最好的?

【问题讨论】:

标签: c++ templates language-lawyer c++20


【解决方案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.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)

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

(2.5) F1F2 是函数模板特化,根据 [temp.func.order ],或者,如果不是这样,

[...]

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

如果我们将#1 设为F1 并将#2 设为F2,我们会发现#1 是一个更好的匹配,因为(2.5) 适用并且在(2.8) 之前考虑。 Clang 11+ 和最新的 MSVC 正确选择 #1 作为此处更好的候选者 (demo)。


第二个例子

对于表达式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 作为更好的候选者 (demo)。

为什么 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) 并在两种情况下都选择了未重写的候选者。

【讨论】:

  • @IICapitano 您在示例中使用了不同的 Clang 版本,Clang 11 有正确的实现,而 Clang 10 没有。 谢谢,这解决了我所有的困惑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-16
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多