【问题标题】:Overloading operator== with `&&` and `const` qualifier cause ambiguity in C++20使用 `&&` 和 `const` 限定符重载 operator== 会导致 C++20 中的歧义
【发布时间】:2021-06-19 23:59:56
【问题描述】:

考虑具有相同&& 限定符和不同const 限定符的两个operator== 重载的struct S

struct S {
  bool operator==(const S&) && { 
    return true;
  }
  bool operator==(const S&) const && { 
    return true;
  }
};

如果我将这两个Soperator== 进行比较:

S{} == S{};

gcc 和 msvc 接受此代码,clang rejects 它与:

<source>:14:7: error: use of overloaded operator '==' is ambiguous (with operand types 'S' and 'S')
  S{} == S{};
  ~~~ ^  ~~~

为什么 clang 认为这里有一个模棱两可的重载解决方案?在这种情况下,非 const 不应该是最佳人选吗?

同样,如果我将两个S 与合成的operator!= 进行比较:

S{} != S{};

gcc 仍然接受这个代码,但是 msvc 和 clang doesn't:

<source>:14:7: error: use of overloaded operator '!=' is ambiguous (with operand types 'S' and 'S')
  S{} != S{};
  ~~~ ^  ~~~

合成的operator!= 突然导致msvc 的歧义似乎很奇怪。哪个编译器是对的?

【问题讨论】:

  • (S&amp;&amp;, S&amp;&amp;) 的候选人是 (S&amp;&amp;, const S&amp;&amp;)(const S&amp;&amp;, const S&amp;&amp;)(const S&amp;&amp;, S&amp;&amp;)/*rewritten*/。每个S&amp;&amp; 都比const S&amp;&amp; 更匹配,所以调用应该是模棱两可的IMO。
  • 我也认为 Clang 是正确的,正如@Jarod42 所指出的那样。 S{} 不是 const,但表达式 S{} == S{} 需要在最佳匹配、每个参数的参数、例如 arg1arg2、四个综合重载和 @ 987654348@ (S&amp;&amp;, const S&amp;&amp;)(S&amp;&amp;, S&amp;&amp;) 是模棱两可的(反之,对于 arg2)。如果我们比较使用函数调用表示法(Clang 接受)S{}.operator(S{}),重载集将是(const S&amp;&amp;, const S&amp;)(S&amp;&amp;, const S&amp;),后者无疑是最佳匹配。

标签: c++ operator-overloading comparison language-lawyer c++20


【解决方案1】:

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

[over.match.oper]

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

  • ...
  • 对于运算符 ,、一元运算符 & 或运算符 ->,内置候选集为空。 对于所有其他运算符,内置候选函数包括 [over.built] 中定义的所有候选运算符函数,与给定运算符相比,
    • 具有相同的操作员名称,并且
    • 接受相同数量的操作数,并且
    • 接受可以根据 [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 的合成反向重载与原始非反向重载有歧义,因为两者都有 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
}

【讨论】:

  • 我有点困惑。 constness 似乎也存在问题。 Here 就是一个例子。有时它会警告歧义,有时会出错,有时没问题。
  • @Timo Constness 是 OP 示例中的问题。你的 A 没问题,因为两个参数都是 const。不能有相同参数列表的合成反向重载,因此没有歧义。 B 与 OP 的示例一样具有不同的常量。可以通过使用 const 限定符将参数设置为与 F 中相同的类型来修复它。C 与 A 一样明确,除了有其他候选者明确地较低等级匹配。 D 像 B 一样模棱两可,但有像 C 这样的额外候选。E 不像 D,因为它本身没有比较运算符(可能是复制粘贴错误)。
  • 哎呀 E 是复制粘贴错误,是的。好的,现在知道了,谢谢。最后一个问题:为什么 B 只是一个警告?
  • @Timo 要获得明确的答案,您需要向 Clang 作者询问。但我怀疑部分原因是通过允许以前定义良好的程序继续作为语言扩展工作来使向后不兼容“更软”。
  • @Timo 是的,比较/重载决议格式不正确。 Clang 可以正确诊断程序的格式错误。其他不诊断问题的编译器不符合标准。
猜你喜欢
  • 2021-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-13
相关资源
最近更新 更多