C++ 比较运算符重载常量与非常量行为



我最近注意到一些我自己无法弄清楚的运算符重载行为。以下两个类仅在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。经过一番挖掘,我发现规则说这些应该在过载解决规则中生成。
  1. 重写的候选者:
  • 对于四个关系运算符表达式 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指出了一些关于不同编译器的有趣发现,这些编译器可以和不能编译不同版本的代码。特别是对于带有标志clanggcc-std=c++2a最新版本x86-64 clang 12.0.0x86-64 gcc 11.1不编译,而旧版本x86-64 clang 9.0.1x86-64 gcc 9.4编译。对于VisualStudio,我们看到类似的模式与标志/std:c++latest。在这里,最新版本x64 msvc v19.28 (VS16.9)不编译,而直接的前身x64 msvc v19.28编译。这些测试是使用编译器资源管理器 godbolt.org 进行的。

  • 特别有趣的是,clanggcc的编译器错误表明问题在于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 相同。看起来它是一个错误/缺失的功能,并在更新的版本中得到了修复。

下面是包含该问题的编译器资源管理器代码段。

最新更新