考虑以下无用代码:
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')
哪个编译器是正确的?如何运算符
来自[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个编译器,可能都应该尽可能迂腐地使用这种类型的代码,尽管不幸的是,在这种情况下,这可能必然会破坏代码的目的。