三向运算符 <=> return 具有隐式转换功能的结构

Three-way operator <=> return struct with implicit conversion function

考虑以下无用代码:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
};

static_assert(S{} <= S{});

Clang 和 MSVC 接受此代码,但 GCC rejects 它带有错误消息:

error: no match for 'operator<=' (operand types are 'S' and 'int')

哪个编译器是正确的? operator<=是如何从operator<=>合成的?

C++20 support in GCC is still experimental, so while it does support the three-way operator,你的 static_assert 失败了,因为其他编译器自动从 <=> 运算符推断出 <= 运算符,而 GCC 似乎更迂腐它对标准的解释,并且由于您没有直接使用 <= 运算符,因此编译器会发出编译时错误,因为它找不到 <= 运算符。

如果添加 <= 运算符,代码有效,示例:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
  constexpr bool operator<=(S) { return true; }
};

static_assert(S{} <= S{});

此外,如果您将断言更改为三向运算符,则测试在所有编译器上都会失败,例如:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
};

static_assert(S{} <=> S{});

此外,由于预期三向运算符 本质上 return 负值、零值或正值(实际上 return 排序), returning *this 可能会将值转换为 Clang 和 MSVC 解释为断言的 true 值的值,而 GCC 可能会将其转换为 false值,因此断言失败。

如果您将 return 类型更改为任何负值(甚至 -0)或零值,断言将通过所有编译器,此外,如果您将该值更改为上述任何正值0 断言在所有编译器上都失败。

您可以更改三向运算符以将 *this 强制转换为 int,这将调用 operator int 和 return 0,这将导致断言通过,示例:

constexpr auto operator<=>(S) const { return static_cast<int>(*this); }

所以直接回答:

Which compiler is right?

根据我使用 GCC 的经验,当涉及到 可能 表面上模棱两可的语言规范时,它倾向于非常迂腐地解释语言并且宁愿犯错误一个奇怪的代码片段(像你的)。

为此,其他编译器可能对语言的解释过于宽松,或者 GCC 可能在此方面过于严格特殊情况。

无论哪种方式,即使此代码“无用”,任何 运行 以所有 3 个编译器为目标的此类代码的人都应该尝试对此类代码尽可能迂腐,不幸的是,在这种情况下,这可能必然会破坏代码的目的。

来自 [over.match.oper] (3.4.1 and 8):

For the relational ([expr.rel]) operators, the rewritten candidates include all non-rewritten candidates for the expression x <=> y.

If a rewritten operator<=> candidate is selected by overload resolution for an operator @, x @ y is interpreted as [...] (x <=> y) @ 0 [...], using the selected rewritten operator<=> candidate. Rewritten candidates for the operator @ are not considered in the context of the resulting expression.

因此,对于表达式 S{} <= S{},所选运算符将为 S::operator<=>(S) const,表达式将重写为 (S{} <=> S{}) <= 0。在重写的表达式中,操作数的类型是 Sint,将选择内置的 operator<=(int, int)。所以最终表达式(在将 S 转换为 int 之后)将导致 0 <= 0,即 true.

总而言之,Clang 和 MSVC 在这种情况下是正确的,GCC 似乎无法将 (S{} <=> S{}) <= 0 解释为对内置运算符的调用(注意错误消息为 operand types are 'S' and 'int')。如果将 static_assert 中的条件更改为重写的表达式 (S{} <=> S{}) <= 0,则 all three compilers accept it.