我最近注意到一些我自己无法弄清楚的运算符重载行为。以下两个类仅在const
上区别于对ClassA
的杆件比较运算符重载。在ClassB
,它们不是恒常的。一般来说,我知道人们总是更喜欢const
,但我仍然对为什么我们看到我将在下面描述的行为感兴趣。
#include <string>
class ClassA {
public:
explicit ClassA(double t) : _t(t) {}
std::string operator<=(int const& other) const {
return "A(<=)";
}
std::string operator==(int const& other) const {
return "A(==)";
}
friend std::string operator<=(int const& other, ClassA const& expr) {
return "A'(<=)";
}
friend std::string operator==(int const& other, ClassA const& expr) {
return "A'(==)";
}
private:
double _t;
};
class ClassB {
public:
explicit ClassB(double t) : _t(t) {}
std::string operator<=(int const& other) {
return "B(<=)";
}
std::string operator==(int const& other) {
return "B(==)";
}
friend std::string operator<=(int const& other, ClassB const& expr) {
return "B'(<=)";
}
friend std::string operator==(int const& other, ClassB const& expr) {
return "B'(==)";
}
private:
double _t;
};
现在,我想在const
和非常量场景中使用这些类和比较函数。
int
main(int argc,
char* argv[]) {
ClassA a1{0};
1==a1; //OK
1<=a1; //OK
ClassA const a2{0};
1==a2; //OK
1<=a2; //OK
ClassB b1{0};
1==b1; //NOT OK
1<=b1; //OK
ClassB const b2{0};
1==b2; //OK
1<=b2; //OK
return 0;
}
一切正常,但我标记的一行NOT OK
.这将引发编译器错误。
error C2446: '==': no conversion from 'ClassB' to 'int'
我的问题分为三个部分,但我希望有一个很好的理由可以回答所有这些问题。所以我希望把它发布到一个单一的 SO 问题中仍然没问题。
当不等式
<=
时,为什么相等运算符==
不编译?为什么成员函数是否const
对友元函数很重要?为什么将ClassB
对象const
修复它?
更新:
- 在评论中@Eljay指出,问题可能是由新的 C++20 功能创建的,该功能自动生成带有倒置参数的比较运算符。这显然使成员
std::string operator==(int const& other)
(重新排列后)更适合1==b1
。经过一番挖掘,我发现规则说这些应该在过载解决规则中生成。
- 重写的候选者:
- 对于四个关系运算符表达式 x<y、x><=y、x>y 和 x>=y,找到的所有成员、非成员和内置运算符 <=> 都是 添加到集合中。
- 对于四个关系运算符表达式 x<y、x><=y、x>y 和 x>=y 以及三向比较表达式 x<=>y,a 两个参数的顺序颠倒的合成候选为 为每个成员、非成员和内置运算符添加<=>找到。
- 对于 x!=y,找到的所有成员、非成员和内置运算符 == 都将添加到集合中。
- 对于相等运算符表达式 x==y 和 x!=y,将两个参数的顺序颠倒的合成候选参数添加为 找到每个成员、非成员和内置运算符 ==。
在所有情况下,重写的候选人都不会在上下文中考虑 重写的表达式。对于所有其他运算符,重写 候选集为空。
@463035818_is_not_a_number指出了一些关于不同编译器的有趣发现,这些编译器可以和不能编译不同版本的代码。特别是对于带有标志
clang
和gcc
-std=c++2a
最新版本x86-64 clang 12.0.0
和x86-64 gcc 11.1
不编译,而旧版本x86-64 clang 9.0.1
和x86-64 gcc 9.4
编译。对于VisualStudio,我们看到类似的模式与标志/std:c++latest
。在这里,最新版本x64 msvc v19.28 (VS16.9)
不编译,而直接的前身x64 msvc v19.28
编译。这些测试是使用编译器资源管理器 godbolt.org 进行的。特别有趣的是,
clang
和gcc
的编译器错误表明问题在于std::string operator==(int const& other)
没有返回bool
。
铛
error: return type 'std::string' (aka 'basic_string<char>') of selected 'operator==' function for rewritten '==' comparison is not 'bool'
1==b1; //NOT OK
海湾合作委员会
error: return type of 'std::string ClassB::operator==(const int&)' is not 'bool'
1==b1; //NOT OK
虽然这些都是非常有趣的见解,但最初的问题仍然悬而未决。
不是一个具体的答案。但是,让我们看一下文档。
重载运算符的返回类型没有限制(因为返回类型不参与重载解析),但有规范实现:
。该语言没有其他限制 重载运算符的作用或返回类型,但通常重载 操作员的行为应尽可能类似于 内置运算符
然后:
..返回类型受表达式的限制,其中 应使用运算符。
例如,赋值运算符 通过引用返回,以便可以编写 a = b = c = d, 因为内置运算符允许这样做。
我们进一步挖掘:
。内置运算符返回 bool 时,大多数用户定义的重载也返回 返回 bool,以便用户定义的运算符可以在同一 方式作为内置。但是,在用户定义的运算符重载中, 任何类型都可以用作返回类型(包括 void)。
甚至更进一步(三向比较):
如果两个操作数都具有算术类型,或者如果一个操作数具有无作用域 枚举型和另一种有整型,通常的算术 转换将应用于操作数。
因此,我会断言这取决于实现。在我的机器上,它编译(g++)并运行:
std::cout << (1==b1) << std::endl; // Prints B'(==)
微小的重新更新
@463035818_is_not_a_number:">这个问题在VS中出现了。较新版本的 gcc 也拒绝这种用法,与 clang 相同。看起来它是一个错误/缺失的功能,并在更新的版本中得到了修复。
下面是包含该问题的编译器资源管理器代码段。