问题描述
考虑以下无用代码:
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<=>
合成的?
解决方法
来自 [over.match.oper](3.4.1 和 8):
对于关系 ([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
,则 all three compilers accept it。
C++20 support in GCC is still experimental,因此虽然它 does support three-way operator,但您的 static_assert
失败了,因为其他编译器会自动从 {{ 1}} 运算符,而 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{});
可能会将值转换为Clang 和 MSVC 将其解释为断言的 struct S{
constexpr operator int() const { return 0; }
constexpr auto operator<=>(S) const { return *this; }
};
static_assert(S{} <=> S{});
值,而 GCC 可能会将其转换为 *this
值,因此断言失败。
如果您将返回类型更改为任何负值(甚至 true
)或零值,则断言会通过所有编译器,此外,如果您将值更改为大于 0 的任何正值,则断言会在所有编译器上失败编译器。
您可以将三向运算符更改为将 false
强制转换为 -0
,后者将调用 *this
并返回 0,这将导致断言通过,例如:>
int
所以直接回答:
哪个编译器是正确的?
根据我对 GCC 的经验,它倾向于非常迂腐地解释语言,并且在涉及到一个在奇怪的代码片段面前可能模棱两可的语言规范时,会在谨慎方面犯错(就像你的一样)。
为此,其他编译器可能对语言的解释过于松散,或者 GCC 在这种特殊情况下可能过于严格。
无论哪种方式,即使此代码“无用”,任何遇到此类问题且针对所有 3 个编译器的人都应该尝试使用此类代码尽可能迂腐,尽管这可能必然会打败不幸的是,本例中代码的目的。