C++比较运算符重载const与non-const行为的比较

5

我最近注意到一些运算符重载的行为,我自己无法理解。以下两个类只在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问题中。
当不等式运算符`<=`能编译通过时,为什么相等运算符`==`不能编译通过?对于友元函数来说,成员函数是否是`const`很重要吗?为什么使`ClassB`对象成为`const`就能解决问题?
更新:
1. 在评论区@Eljay指出,问题可能是由C++20的一个新特性引起的,该特性会自动生成具有反向参数的比较运算符。这显然使得成员函数`std::string operator==(int const& other)`(重新排列后)更适合于`1==b1`。经过一番搜索,我找到了规则,说明这些应该在overload resolution的规则中生成。
2. 重写的候选方法:
a. 对于四个关系运算表达式xy和x>=y,将找到的所有成员、非成员和内置operator<=>添加到集合中。 b. 对于四个关系运算表达式xy和x>=y以及三路比较表达式x<=>y,对于找到的每个成员、非成员和内置operator<=>,都会添加一个候选方法,其参数顺序被翻转。 c. 对于x!=y,将找到的所有成员、非成员和内置operator==添加到集合中。 d. 对于相等运算符表达式x==y和x!=y,对于找到的每个成员、非成员和内置operator==,都会添加一个参数顺序被翻转的候选方法。
3. 在所有情况下,重写的候选方法都不考虑在重写表达式的上下文中。对于其他所有运算符,重写的候选方法集为空。
  • @463035818_is_not_a_number指出了关于不同版本编译器能否编译代码的一些有趣发现。特别是对于带标志-std=c++2aclanggcc,最新版本的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可以编译。这些测试是使用Compiler explorergodbolt.org进行的。

  • 特别有趣的是,clanggcc的编译器错误表明问题在于std::string operator==(int const& other)没有返回bool

clang

error: return type 'std::string' (aka 'basic_string<char>') of selected 'operator==' function for rewritten '==' comparison is not 'bool'
    1==b1; //NOT OK

gcc

error: return type of 'std::string ClassB::operator==(const int&)' is not 'bool'
    1==b1; //NOT OK

尽管这些都是非常有趣的见解,但原始问题仍然没有得到解答。

3
operator== 没有返回一个 bool。这会导致 C++20 中的 spaceship 合成器不满意。 - Eljay
1
我的C++20智能感知给了我一个稍微更详细的答案:“函数“ClassB :: operator ==”...选择进行操作符重写的函数不返回bool类型”。看起来非constB正在促使比较合成,但由于operator==没有适当的语义,因此失败了。而且,可能是因为缺少<=>,所以<=可以正常工作,并成功使用自由函数operator<= - Nathan Pierson
1
我认为发生的情况是,在太空船合成器和重新排列之后,ClassB成员bool operator==(int const&)比友元operator==更适合于1==b1; //NOT OK,因为friend有一个const ClassB引用而b1不是const。 - Eljay
1
@Swift-FridayPie https://godbolt.org/z/cM7K1a6Mn。我想我会添加`language-lawyer`。 - 463035818_is_not_a_number
1
@SimonT,你考虑重新提出问题吗?可以参考这里:https://godbolt.org/z/cM7K1a6Mn。你的代码在旧版本的gcc和clang中编译通过,但是在最新版本中都被拒绝了。看起来像是一个错误/缺失功能,最近才被“修复”。另一方面,“operator==”的返回类型不是“bool”作为错误看起来很奇怪,我不想相信它。或许也要加上“language-lawyer”。 - 463035818_is_not_a_number
显示剩余16条评论
1个回答

1

这不是一个具体的答案。但是,让我们看一下文档

对于重载运算符,没有返回类型限制(因为返回类型不参与重载决议),但有规范实现

......语言在重载运算符所做的事情上没有其他限制,或者对返回类型没有限制,但通常期望重载运算符的行为尽可能类似于内置运算符。

然后:

......返回类型由预期使用运算符的表达式限制

例如,赋值运算符返回引用,以使得写a = b = c = d成为可能,因为内置运算符允许这样做。

我们深入挖掘进一步

在内置运算符返回bool的情况下,大多数用户定义的重载也会返回bool,以便可以像使用内置运算符一样使用用户定义的运算符。但是,在用户定义的运算符重载中,任何类型都可以用作返回类型(包括void)。
甚至还有further (三路比较)
如果两个操作数具有算术类型,或者一个操作数具有未作用域的枚举类型,另一个操作数具有整数类型,则通常的算术转换将应用于操作数。
因此,我会断言它取决于实现。在我的机器上,它可以编译(g++)并运行:
std::cout << (1==b1) << std::endl; // Prints B'(==)

微小的重新更新

@463035818_is_not_a_number“这个问题在VS中被发现。较新版本的gcc也会拒绝这样的用法,clang也是如此。看起来更像是一个错误/缺失的功能,在更近期的版本中得到了修复。”

这里是带有该问题的编译器资源管理器片段


请注意,这并不是仅限于VS,较新版本的gcc也会拒绝它,clang也是如此,这似乎更像是一个错误/缺失的功能,并在较新的版本中得到了修复:https://godbolt.org/z/cM7K1a6Mn - 463035818_is_not_a_number

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接