提问者:小点点

具有隐式转换函数的三路运算符<=>返回结构


考虑以下无用代码:

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')

哪个编译器是正确的?如何运算符


共2个答案

匿名用户

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

对于关系([excr. rel])运算符,重写的候选对象包括表达式x的所有未重写的候选对象

如果重写的运算符

所以对于表达式S{}

总之,Clang和MSVC在这种情况下是正确的,GCC似乎无法解释(S{}

匿名用户

GCC中的C20支持仍然是实验性的,所以虽然它确实支持三方运算符,但您的static_assert失败了,因为其他编译器正在自动推断

如果您添加<代码>

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{});

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

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的任何正值,断言将在所有编译器上失败。

您可以更改三方运算符以将*this转换为int,它将调用运算符int并返回0,然后导致断言通过,例如:

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

所以直接回答:

哪个编译器是正确的?

根据我对GCC的经验,当涉及到语言规范时,它倾向于非常迂腐地解释语言,并且在面对奇怪的代码片段时可能会犯错误(比如你的代码片段)。

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

无论哪种方式,即使这段代码是“无用的”,任何遇到类似问题的人,如果目标是所有3个编译器,可能都应该尽可能迂腐地使用这种类型的代码,尽管不幸的是,在这种情况下,这可能必然会破坏代码的目的。