【问题标题】:Ambiguity in case of multiple inheritance and spaceship operator in C++20C++20中多重继承和宇宙飞船运算符的歧义
【发布时间】:2021-11-13 08:09:54
【问题描述】:

在下面的简化程序中,结构体C 继承自两个结构体AB。前者定义了 spaceship operator <=> 和 less 运算符,后者 - 仅定义了 spaceship 运算符。然后对C类的对象执行较少的操作:

#include <compare>

struct A { 
    auto operator <=>(const A&) const = default;
    bool operator <(const A&) const = default;
};
struct B { 
    auto operator <=>(const B&) const = default; 
};
struct C : A, B {};
int main() { 
    C c;
    c.operator<(c); //ok everywhere
    return c < c;   //error in GCC
}

这里令人惊讶的是,显式调用 c.operator&lt;(c) 在所有编译器中都成功,但类似的调用 c&lt;c 被 Clang 允许但在 GCC 中被拒绝:

error: request for member 'operator<=>' is ambiguous
<source>:8:10: note: candidates are: 'auto B::operator<=>(const B&) const'
<source>:4:10: note:                 'constexpr auto A::operator<=>(const A&) const'

演示:https://gcc.godbolt.org/z/xn7W9PaPc

有一个可能相关的问题:GCC can't differentiate between operator++() and operator++(int)。但是在那个问题中,显式运算符 (++) 调用被所有编译器拒绝,这与这个问题不同,显式运算符调用被所有人接受。

我以为C中只有一个operator &lt;,它是从A派生的,根本不用考虑starship运算符。是这样吗?这里有什么编译器?

【问题讨论】:

  • 你使用 C++20 编译器吗?
  • @jacobgalam starship 运算符从 C++20 开始可用。因此,它无法在没有...的情况下编译(顺便说一句。它在 Compiler Explorer 上的链接演示代码中使用。)
  • 问题不在于 spaceship 运算符,问题在于多重继承,当 c &lt; c 时,您的编译器会混淆要遵循哪个路径来评估 &lt;=&gt; 运算符。

标签: c++ language-lawyer overloading multiple-inheritance c++20


【解决方案1】:

gcc 在这里是正确的。

当你这样做时:

c.operator<(c);

您正在对字面上名为 operator&lt; 的东西执行名称查找。只有一个这样的函数(A 中的那个)所以成功了。

但是当您执行c &lt; c 时,您并没有在查找operator&lt;。你在做两件事:

  1. c &lt; c 的特定查找,可找到 operator&lt; 候选者(成员、非成员或内置)
  2. 找到c &lt;=&gt; c的所有重写候选者

现在,第一次查找成功并找到与以前相同的A::operator&lt;。但第二次查找失败 - 因为c &lt;=&gt; c 不明确(在AB 中的候选人之间)。来自[class.member.lookup]/6 的规则是:

搜索的结果是S(N,T)的声明集。 如果它是一个无效集合,则程序是非良构的。

我们有一个无效的集合作为搜索的结果,所以程序是非良构的。不是我们什么都没找到,而是整个查找失败了。仅仅因为在这种情况下我们正在查找重写的候选人而不是主要候选人并不重要,它仍然是一个失败的查找。


它失败实际上很好,因为如果我们以通常的方式解决这个模棱两可的合并集问题:

  struct C : A, B {
+     using A::operator<=>;
+     using B::operator<=>;
  };

那么我们的查找将是模棱两可的!因为现在我们对重写的候选者的查找找到了两个operator&lt;=&gt;s,所以我们最终得到了三个候选者:

  1. operator&lt;(A const&amp;, A const&amp;)
  2. operator&lt;=&gt;(A const&amp;, A const&amp;)
  3. operator&lt;=&gt;(B const&amp;, B const&amp;)

12 好(因为主要候选者比重写的候选者更好),但 13 是模棱两可的(两者都不比另一个更好)。

因此,原版失败,而这版也失败了,这很好:作为课程作者,您应该想出正确的做法 - 因为不清楚那是什么。

【讨论】:

  • 谢谢!澄清一下,如果我们从A 中删除operator &lt;=&gt;,那么程序将保持格式错误,因为其余两个候选人operator&lt;(A const&amp;, A const&amp;)operator&lt;=&gt;(B const&amp;, B const&amp;) 都不比另一个更好?但是 GCC 接受这样的程序:gcc.godbolt.org/z/9rPrzfxcP
  • @Fedor gcc 实现了一些与标准规则略有不同的东西,以捕捉其他一些破损。
【解决方案2】:

我向 Microsoft 报告了这个问题,他们告诉我他们和 Clang 的行为是正确的,而 GCC 是错误的:https://developercommunity.visualstudio.com/t/False-acceptance-of-ambiguity-in-case-of/1534112

为了完整起见,我在这里引用他们的答案:

您观察到的编译器行为是按照https://cdacamar.github.io/wg21papers/proposed/spaceship-dr.html 中概述的解决方案设计的。原因是编译器会发现A::operator&lt;=&gt;A::operator&lt;B::operator&lt;=&gt; 作为可能的重载决议候选者。因为A::operator&lt; 不需要重写表达式,所以编译器不会考虑A::operator&lt;=&gt;,因为A::operator&lt; 是在具有相同签名的相同范围内声明的,所以唯一剩下的候选者是A::operator&lt;B::operator&lt;=&gt;,但由于&lt;=&gt;需要重写稍后删除的表达式并选择A::operator&lt;。您可以在此处观察到 Clang 具有相同的行为:https://godbolt.org/z/M6ffr95Ej

【讨论】:

    猜你喜欢
    • 2021-08-07
    • 2011-03-04
    • 1970-01-01
    • 2010-10-24
    • 2021-04-23
    • 2021-07-27
    • 1970-01-01
    相关资源
    最近更新 更多