我最近注意到一些我自己无法理解的运算符重载行为。以下两个类仅在ClassA
的成员比较运算符重载上的const
不同。在ClassB
中,它们不是const。通常我知道人们总是更喜欢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
和非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问题中仍然没有问题。
为什么等号操作符==
不编译,当不等式
std::字符串运算符==(int const
在所有情况下,在重写表达式的上下文中不考虑重写的候选集。对于所有其他运算符,重写的候选集是空的。
>
@463035818_is_not_a_number指出了一些关于不同编译器的有趣发现,它们可以和不能编译不同版本的代码。特别是对于带有-std=c 2a
标志的clang
和gcc
,最新版本的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最新
。这里最新版本x64 msvc v19.28(VS16.9)
不编译,而直接前身x64 msvc v19.28
编译。这些测试是用编译器资源管理器godbolt.org的。
特别有趣的是,注意到clang
和gcc
的编译器错误表明问题在于std::字符串操作符==(int const
叮当
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也是如此。它看起来更像是一个bug/缺失的功能,并在最近的版本中得到了修复。”
这是带有该问题的编译器资源管理器片段。