三向运算符 <=> 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
。在重写的表达式中,操作数的类型是 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.
考虑以下无用代码:
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 anoperator @
,x @ y
is interpreted as [...](x <=> y) @ 0
[...], using the selected rewrittenoperator<=>
candidate. Rewritten candidates for theoperator @
are not considered in the context of the resulting expression.
因此,对于表达式 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.