考虑以下两个用于s
的重载运算符<=>
:
#include <compare>
struct S {};
int operator<=>(S, int) { return 0; } #1
S operator<=>(S, S) { return {}; } #2
如果我将对象S
与int
进行比较,#1
将为我生成正确的运算符,所以像S{}<=0
、0
或0<=>S{}
这样的表达式就可以了。
但如果我将一个对象s
与其他对象s
:
S{} < S{};
则将重写为(S{}<=>S{})<0
。由于(S{}<=>S{})
将返回另一个S
,所以我们回到原始问题:S
与int
进行比较。此时,我们没有运算符<(S,int)
,因此#1
将为我生成正确的运算符。
但令人惊讶的是,三个编译器都没有这样对我。GCC、Clang和MSVC都拒绝S{}
并带有相同的错误消息:
no match for 'operator<' (operand types are 'S' and 'int')
这让我很沮丧。因为#1
实际存在。为什么不在这里产生运算符的嵌套生成?标准是怎么说的?是否存在静态约束冲突?
这是错误的,尽管错误消息是相当混乱的。
[over.match.oper]/8中的规则是(强调我的):
如果通过重载解析为运算符@
选择了重写的运算符<=>
候选,则如果选定的候选是参数顺序相反的合成候选,则x@y
被解释为0@(y<=>x)
;否则,则使用选定的重写的运算符<=>
候选。在结果表达式的上下文中不考虑操作符@
的重写候选项。
表达式S{}
将解析为重写的候选(S{}<=>S{})<0
。结果表达式将不会在其查找中考虑重写的候选项。因此,当我们执行s{}<0
时,只需要查找一个运算符<
,而不是运算符<=>
。它找不到这样的东西,所以表达方式是畸形的。
<source>:8:14: error: no match for 'operator<' (operand types are 'S' and 'int')
8 | auto x = S{} < S{};
| ~~~~^~~~~
在这个意义上,错误是真的:没有匹配,特别是operator<
与这些操作数。尽管如果错误消息中有更多的上下文来解释为什么要查找这些信息,这将会有所帮助。