首页 > 解决方案 > 重写比较运算符会带来哪些重大变化?

问题描述

在 C++20 中有一些关于重写比较运算符的新规则,我试图了解它们是如何工作的。我遇到了以下程序

struct B {};

struct A
{
    bool operator==(B const&);  // #1
};

bool operator==(B const&, A const&);  // #2

int main()
{
  B{} == A{};  // C++17: calls #2
               // C++20: calls #1
}

这实际上破坏了现有代码。我对此感到有些惊讶;#2实际上对我来说仍然看起来更好:p

那么这些新规则如何改变现有代码的含义呢?

标签: c++language-lawyerc++20comparison-operators

解决方案


该特定方面是一种简单的重写形式,即反转操作数。主运算符==<=>可以颠倒,辅助运算符!=, <, >, <=, 和>=, 可以根据主运算符重写。

倒车方面可以用一个相对简单的例子来说明。

如果您没有B::operator==(A)要处理的特定b == a内容,则可以使用相反的方法来代替:A::operator==(B). 这是有道理的,因为相等是一种双向关系:(a == b) => (b == a).

另一方面,对二级运算符的重写涉及使用不同的运算符。考虑a > b。如果您无法找到直接执行此操作的函数,例如A::operator>(B),该语言将寻找诸如A::operator<=>(B)然后简单地从中计算结果之类的东西。

这是对过程的简单看法,但我的大多数学生似乎都理解这一点。如果您想了解更多详细信息,请参阅[over.match.oper]C++20 部分,重载决议的一部分(@是运算符的占位符):

对于关系和等式运算符,重写的候选包括所有成员、非成员和内置候选,<=>其重写表达式(x <=> y) @ 0使用 that 格式正确operator<=>

对于关系运算符、相等运算符和三向比较运算符,重写的候选还包括一个合成候选,两个参数的顺序颠倒,对于每个成员、非成员和内置候选的运算符<=>,使用 that重写的表达式0 @ (y <=> x)是格式良好的operator<=>


因此,必须提供真正的operator==and operator<,然后进行样板化的日子已经一去不复返了:

operator!=      as      !  operator==
operator>       as      ! (operator== || operator<)
operator<=      as         operator== || operator<
operator>=      as      !  operator<

如果我弄错了其中一个或多个,请不要抱怨,这只是说明了我的观点,即 C++20 有多好,因为您现在只需提供一个最小集合(很可能只是operator<=>加上您想要的任何其他内容为了效率)并让编译器照顾它:-)


关于为什么选择一个而不是另一个的问题可以通过以下代码来辨别:

#include <iostream>

struct B {};
struct A {
    bool operator==(B const&) { std::cout << "1\n"; return true; }
};
bool operator==(B const&, A const&) { std::cout << "2\n"; return true; }

int main() {
  auto b = B{}; auto a = A{};

           b ==          a;  // outputs: 1
  (const B)b ==          a;  //          1
           b == (const A)a;  //          2
  (const B)b == (const A)a;  //          2
}

其输出表明这是决定哪个是更好的候选者的const-ness 。a

顺便说一句,您可能想看看这篇文章,它提供了更深入的了解。


推荐阅读