考虑具有两个运算符==
重载相同的
struct
S
struct S {
bool operator==(const S&) && {
return true;
}
bool operator==(const S&) const && {
return true;
}
};
如果我将两个S
与运算符==
进行比较:
S{} == S{};
gcc和msvc接受此代码,clang拒绝它:
<source>:14:7: error: use of overloaded operator '==' is ambiguous (with operand types 'S' and 'S')
S{} == S{};
~~~ ^ ~~~
为什么clang认为这里有一个模棱两可的重载解决方案?在这种情况下,非const不是应该是最好的候选者吗?
同样,如果我将两个S
与合成的运算符进行比较!=
:
S{} != S{};
gcc仍然接受此代码,但msvc和clang不接受:
<source>:14:7: error: use of overloaded operator '!=' is ambiguous (with operand types 'S' and 'S')
S{} != S{};
~~~ ^ ~~~
合成的运算符似乎很奇怪!=
突然导致msvc的歧义。哪个编译器是正确的?
这个例子在C 17中是明确的。C 20带来了变化:
[over. match.oper]
对于具有cv1 T1类型操作数的一元操作符@和具有cv1 T1类型左操作数和cv2 T2类型右操作数的二元操作符@,四组候选函数,指定成员候选函数,非成员候选函数,内置候选函数和重写候选函数,构造如下:
重写后的候选集确定如下:
因此,重写的候选集包括以下内容:
implicit object parameter
|||
(S&&, const S&); // 1
(const S&&, const S&); // 2
// candidates that match with reversed arguments
(const S&, S&&); // 1 reversed
(const S&, const S&&); // 2 reversed
重载1比2更匹配,但是1的合成反向重载与原始非反向重载是不明确的,因为两者都将const转换为一个参数。请注意,即使重载2不存在,这实际上也是不明确的。
因此,Clang是正确的。
这也包含在信息兼容性附件中:
受影响的子句:[over. match.oper]更改:平等和不平等表达式现在可以找到颠倒和重写的候选项。
理由:通过三向比较提高相等的一致性,并更容易编写相等操作的完整补码。
对原始特征的影响:两个不同类型的对象之间的相等和不等式表达式,其中一个可以转换为另一个,可能会调用不同的运算符。同一类型的两个对象之间的相等和不等式表达式可能会变得模糊。
struct A {
operator int() const;
};
bool operator==(A, int); // #1
// #2 is built-in candidate: bool operator==(int, int);
// #3 is built-in candidate: bool operator!=(int, int);
int check(A x, A y) {
return (x == y) + // ill-formed; previously well-formed
(10 == x) + // calls #1, previously selected #2
(10 != x); // calls #1, previously selected #3
}