c++ - C++ 比较运算符重载 const 与非 const 行为
问题描述
我最近注意到一些我自己无法弄清楚的运算符重载行为。以下两个类仅在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, and x>=y 以及三路比较表达式 x<=>y,两个参数顺序颠倒的合成候选为找到的每个成员、非成员和内置运算符<=> 添加。
- 对于 x!=y,所有找到的成员、非成员和内置运算符 == 都将添加到集合中。
- 对于相等运算符表达式 x==y 和 x!=y,为找到的每个成员、非成员和内置运算符 == 添加一个合成候选,其中两个参数的顺序颠倒。
在所有情况下,都不会在重写表达式的上下文中考虑重写的候选。对于所有其他运算符,重写的候选集为空。
@463035818_is_not_a_number 指出了一些关于不同编译器的有趣发现,这些编译器可以和不能编译不同版本的代码。专门针对
clang
并gcc
带有标志-std=c++2a
的最新版本不编译,而旧版本x86-64 clang 12.0.0
则做。对于 VisualStudio,我们看到带有标志的类似模式。在这里,最新版本无法编译,而直接前身则可以。这些测试是使用 Compiler explorer godbolt.org 进行的。x86-64 gcc 11.1
x86-64 clang 9.0.1
x86-64 gcc 9.4
/std:c++latest
x64 msvc v19.28 (VS16.9)
x64 msvc v19.28
特别有趣的是,编译器错误
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)。
更进一步(三向比较) :
如果两个操作数都具有算术类型,或者如果一个操作数具有无作用域枚举类型而另一个具有整数类型,则通常的算术转换将应用于操作数。
所以,我会断言它取决于 implementation。在我的机器上它编译(g++)并运行:
std::cout << (1==b1) << std::endl; // Prints B'(==)
微小的重新更新
@463035818_is_not_a_number:“在 VS 中发现了这个问题。新版本的 gcc 也拒绝这种用法,与 clang 相同。看起来它是一个错误/缺失的功能,并在更新的版本中得到了修复。”
这是有问题的编译器资源管理器片段。
推荐阅读
- angular - 在 Gitlab 上运行 CI 管道时出错
- java - 如何使用 mysql 解决 Spring Boot 应用程序上的“通信链接失败”问题?
- ssl - MAMP Pro (5.5.1) 和正式签署的 SSL 证书
- windows - UWP如何获得内容窗格投影的NavigationView内阴影?
- python - 从深度图像生成点云
- emacs - 协助描绘树或框指针结构
- docker - 在 Chrome 上通过 localhost 访问 kubernetes 服务失败
- git - 更改所有提交的 git 作者信息对我的一个存储库有效,但对其他存储库无效,为什么?
- swift - 是否有一种 Sandbox 和 AppleStore 精明的方法可以以编程方式让机器进入睡眠/关闭状态?
- java - 如何更改 Win 10 中 `mvn` cmd 使用的运行时?