7

考虑以下无用的代码:

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

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

Clang 和 MSVC 接受此代码,但 GCC拒绝它并显示错误消息:

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

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

4

2 回答 2

4

从 [over.match.oper] (3.4.18):

对于关系 ([expr.rel]) 运算符,重写的候选包括表达式 x <=> y 的所有未重写的候选。

如果operator<=>通过重载决议选择了重写的候选者operator @x @ y则使用选定的重写候选者将其解释为 [...] (x <=> y) @ 0[...] operator<=>operator @在结果表达式的上下文中不考虑重写的候选者。

因此,对于表达式S{} <= S{},选定的运算符将是S::operator<=>(S) const并且表达式将被重写为(S{} <=> S{}) <= 0. 在重写的表达式中,操作数的类型是S和,将选择int内置函数。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,则所有三个编译器都会接受它

于 2021-03-17T11:38:58.490 回答
0

GCC 中的 C++20 支持仍处于试验阶段,因此虽然它确实支持三向运算符,但您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{});

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

如果您将返回类型更改为任何负值(偶数-0)或零值,则断言将传递给所有编译器,此外,如果您将值更改为任何高于 0 的正值,则断言在所有编译器上都会失败。

您可以将三路运算符更改为*thisint调用operator int并返回 0 的类型,这将导致断言通过,例如:

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

所以直接回答:

哪个编译器是对的?

根据我使用 GCC 的经验,当涉及到面对奇怪的代码片段(如您的代码片段)可能模棱两可的语言规范时,它往往会非常迂腐地解释该语言,并且在谨慎方面犯错。

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

无论哪种方式,即使这段代码是“无用的”,任何遇到这样的事情并针对所有 3 个编译器的人都应该尽可能地尝试使用这种类型的代码,尽管这可能必然会违背不幸的是,这种情况下的代码。

于 2021-03-17T07:35:24.153 回答