C++ 重载运算符==带`&&;`和'const'限定符在C+中引起歧义+;20

C++ 重载运算符==带`&&;`和'const'限定符在C+中引起歧义+;20,c++,operator-overloading,comparison,language-lawyer,c++20,C++,Operator Overloading,Comparison,Language Lawyer,C++20,考虑具有两个操作符==重载的结构限定符和不同的常量限定符: struct S { bool operator==(const S&) && { return true; } bool operator==(const S&) const && { return true; } }; 如果我将两个S与运算符==进行比较: S{} == S{}; gcc和msvc接受此代码,并将其与: gcc仍然接受此代码,但m

考虑具有两个
操作符==
重载的
结构
限定符和不同的
常量
限定符:

struct S {
  bool operator==(const S&) && { 
    return true;
  }
  bool operator==(const S&) const && { 
    return true;
  }
};
如果我将两个
S
运算符==
进行比较:

S{} == S{};
gcc和msvc接受此代码,并将其与:

gcc仍然接受此代码,但msvc和clang:

:14:7:错误:使用重载运算符“!=”不明确(操作数类型为“S”和“S”)
S{}=S{};
~~~ ^  ~~~

合成的
操作符看起来很奇怪=突然导致msvc的歧义。哪个编译器是对的?

这个例子在C++17中是明确的。C++20带来了变化:

[结束,比赛,开始]

对于具有cv1 T1类型操作数的一元运算符@,以及具有cv1 T1类型左操作数和cv2 T2类型右操作数的二元运算符@,四组候选函数、指定成员候选函数、非成员候选函数、内置候选函数、和重写候选函数的构造如下:

  • 对于运算符、、一元运算符和或运算符->,内置候选集为空对于所有其他运算符,内置候选运算符包括[over.build]中定义的所有候选运算符函数,与给定运算符相比,
    • 具有相同的操作员名称,并且
    • 接受相同数目的操作数,并且
    • 接受可根据[over.best.ics]将给定操作数转换为的操作数类型,以及
    • 不具有与任何非函数模板专用化的非成员候选项相同的参数类型列表
重写的候选集确定如下:

  • 对于等式运算符,对于表达式y==x的每个未重写候选项,重写候选项还包括一个合成候选项,两个参数的顺序颠倒
因此,重写的候选集包括以下内容:

 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与原始的非反向重载不明确,因为两者都有到一个参数的常量转换。注意,即使重载2不存在,这实际上也是不明确的

因此,叮当声是正确的


这也包含在信息兼容性附录中:

受影响的子类:[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
}

(S&&,S&&)
的候选项为
(S&&,常数S&&)
(常数S&,常数S&)
(常数S&,S&)/*重写*/
。每个
S&&
都比
常量S&&
更匹配,所以调用在我看来应该是不明确的。正如@Jarod42所指出的,我也认为Clang是正确的
S{}
不是常量,但是表达式
S{}==S{}
需要在四个合成重载中的最佳匹配、每个参数的参数(比如
arg1
arg2
)之间进行选择,
arg1
(S&&,常量S&)
对于
(S&&&,S&)来说将是模棱两可的(与之相反,对于
arg2
)。如果我们与使用函数调用表示法(Clang接受)时的
S{}.operator(S{})
进行比较,重载集将是
(const S&&,const S&)
(S&,const S&)
,后者显然是最好的匹配。我有点困惑。似乎常数也有问题。这是一个例子。有时它警告歧义,有时它出错,有时它正常。@Timo constness是OP例子中的问题。你的a正常,因为两个参数都是常数。不能进行合成ed使用相同的参数列表反转重载,因此没有歧义。B与OP的示例具有不同的常量。可以通过使用常量限定符将参数设置为相同的类型来修复它,就像您在F中所做的那样。C与A一样明确,只是有其他候选项是明确的低秩匹配。D与B一样具有歧义,但具有像C.E这样的额外候选者不像D,因为它本身没有比较运算符(可能是复制粘贴错误).Whoops E是一个复制粘贴错误,是的。好的,现在知道了,谢谢。最后一个问题tho:为什么B只是一个警告?@Timo要得到确切的答案,你需要问一下Clang作者。但我怀疑部分原因是为了使向后不兼容“更柔和”通过允许以前定义良好的程序继续作为语言扩展工作。@Timo Yes,比较/重载解析格式不正确。Clang可以诊断程序格式不正确。其他未诊断此问题的编译器不符合标准。
<source>:14:7: error: use of overloaded operator '!=' is ambiguous (with operand types 'S' and 'S')
  S{} != S{};
  ~~~ ^  ~~~
 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
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
}