【问题标题】:C++ Comparison Operator Overloading const vs non-const BehaviourC++ 比较运算符重载 const 与非 const 行为
【发布时间】:2021-06-11 14:08:36
【问题描述】:

我最近注意到一些我自己无法弄清楚的运算符重载行为。以下两个类仅在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 和非常量场景中使用这些类和比较函数。

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 问题中仍然可以。

为什么相等运算符 == 不编译,而不等式 &lt;= 是?为什么成员函数是否为const 对友元函数很重要?为什么要让ClassB 对象const 修复它?

更新:

  • 在 cmets 中,@Eljay 指出问题可能是由新的 C++20 功能造成的,该功能会自动生成带有倒置参数的比较运算符。这显然使成员std::string operator==(int const&amp; other)(重新安排后)更适合1==b1。经过一番挖掘,我发现规则说这些应该在overload resolution的规则中生成。
  1. 重写的候选人:
  • 对于四个关系运算符表达式 xy 和 x>=y,找到的所有成员、非成员和内置运算符 都是 添加到集合中。
  • 对于四个关系运算符表达式 xy 和 x>=y 以及三路比较表达式 xy,a 两个参数顺序颠倒的合成候选是 为找到的每个成员、非成员和内置运算符 添加。
  • 对于 x!=y,所有找到的成员、非成员和内置运算符 == 都将添加到集合中。
  • 对于等式运算符表达式 x==y 和 x!=y,添加两个参数顺序颠倒的合成候选 已找到每个成员、非成员和内置 operator==。

在所有情况下,都不会在上下文中考虑重写的候选者 的重写表达式。对于所有其他运算符,重写 候选集为空。

  • @463035818_is_not_a_number 指出了一些关于不同编译器的有趣发现,这些编译器可以和不能编译不同版本的代码。专门针对带有 -std=c++2a 标志的 clanggcc,最新版本 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 explorer godbolt.org 进行的。

  • 特别有趣的是clanggcc 的编译器错误提示问题是std::string operator==(int const&amp; 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

gcc

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

虽然这些都是非常有趣的见解,但最初的问题仍然悬而未决。

【问题讨论】:

  • operator== 没有返回 bool。这使得 C++20 宇宙飞船合成器脾气暴躁。
  • 我的 C++20 intellisense 给了我一个更丰富的答案:function "ClassB::operator==" ... selected for operator rewrite does not return type bool。看起来非const B 正在提示比较合成失败,因为operator== 没有适当的语义。而&lt;= 大概是可以工作的,因为&lt;=&gt; 的缺失意味着它甚至没有尝试合成&lt;= 并成功使用了免费功能operator&lt;=
  • 我认为发生的事情是 ClassB 成员 bool operator==(int const&amp;)(在宇宙飞船合成器和重新编曲之后)比朋友 operator== 更适合 1==b1; //NOT OK,因为 friendconst ClassB 引用和 b1 不是 const。
  • @Swift-FridayPie godbolt.org/z/cM7K1a6Mn。我想我会添加language-lawyer
  • @SimonT 稍微改一下你的问题怎么样?考虑一下:godbolt.org/z/cM7K1a6Mn。您的代码使用 gcc 和 clang 与旧版本编译,但都拒绝最新版本中的代码。它几乎看起来像是一个错误/缺失的功能,并且最近被“修复”了。另一方面,“operator== 的返回类型不是bool”作为错误看起来很奇怪,我不想相信它。也许还添加language-lawyer

标签: c++ compiler-errors operator-overloading language-lawyer c++20


【解决方案1】:

不是一个具体的答案。不过,让我们看看documentation

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

...语言没有其他限制 关于重载运算符的作用或返回类型,但一般来说,重载 操作员的行为应尽可能与 内置运算符

然后:

..返回类型受表达式的限制 预计将使用运算符

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

我们挖further:

...其中内置运算符返回 bool,大多数用户定义的重载也是 return bool 以便用户定义的运算符可以在相同的情况下使用 方式作为内置插件。但是,在用户定义的运算符重载中, 任何类型都可以用作返回类型(包括 void)。

甚至further(三通比较)

如果两个操作数都有算术类型,或者如果一个操作数没有作用域 枚举类型,另一个有整数类型,通常的算术 转换应用于操作数。

所以,我会断言它取决于实现。在我的机器上它编译 (g++) 并运行:

std::cout << (1==b1) << std::endl; // Prints B'(==)

微小的重新更新

@463035818_is_not_a_number: “这个问题出现在 VS 中。较新版本的 gcc 也拒绝这种用法,与 clang 相同。它看起来像是一个错误/缺失的功能,并在更新的版本中得到了修复。”

这是有问题的compiler explorer snippet

【讨论】:

  • 请注意,它并不是 VS 真正独有的,新版本的 gcc 也拒绝它,与 clang 一样,它看起来像是一个错误/缺失的功能,并在更新的版本中得到了修复: godbolt.org/z/cM7K1a6Mn
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-20
  • 2020-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-16
相关资源
最近更新 更多