重写的比较运算符会导致哪些重大变化?

cig*_*ien 43 c++ comparison-operators language-lawyer c++20

在 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
}
Run Code Online (Sandbox Code Playgroud)

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

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

pax*_*blo 32

这个特殊的方面是一种简单的重写形式,反转操作数。主运营商==<=>可以颠倒,次级线圈!=<><=,和>=,可以在初选的术语重写。

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

如果您没有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使用operator<=>.

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


因此,不得不提供一个真正的operator==operator<然后锅炉电镀的日子已经一去不复返了:

operator!=      as      !  operator==
operator>       as      ! (operator== || operator<)
operator<=      as         operator== || operator<
operator>=      as      !  operator<
Run Code Online (Sandbox Code Playgroud)

如果我犯了一个或多个错误,请不要抱怨,这只是说明了我对 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
}
Run Code Online (Sandbox Code Playgroud)

,它的输出表明它是const的-nessa决定哪些是更合适的人选。

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

  • 参数不是 const。该成员是非常数,所以它获胜。 (4认同)

Nic*_*las 22

从非语言律师的角度来看,它是这样工作的。C++20 要求operator==计算两个对象是否相等。相等的概念是可交换的:如果 A == B,则 B == A。因此,如果有两个operator==函数可以被 C++20 的参数反转规则调用,那么您的代码的行为应该是相同的

基本上,C++20 所说的是,如果调用哪个很重要,那么您就错误地定义了“平等”。


所以让我们深入了解细节。我所说的“细节”是指标准中最可怕的一章:函数重载解析。

[over.match.oper]/3定义了为运算符重载构建候选函数集的机制。C++20 通过引入“重写候选”来增加这一点:一组候选函数通过以 C++20 认为在逻辑上等效的方式重写表达式而发现。这仅适用于关系和 in/equality 运算符。

该集合是根据以下内容构建的:

  • 对于关系 ([expr.rel]) 运算符,重写的候选项包括表达式 x <=> y 的所有未重写的候选项。
  • 对于关系([expr.rel])和三路比较([expr.spaceship])算子,重写的候选者还包括一个合成的候选者,两个参数的顺序颠倒了,对于每个未重写的候选者表达式 y <=> x。
  • 对于 != 运算符 ([expr.eq]),重写的候选项包括表达式 x == y 的所有未重写的候选项。
  • 对于等式运算符,对于表达式 y == x 的每个未重写候选,重写候选还包括一个合成候选,两个参数的顺序颠倒。
  • 对于所有其他算子,重写的候选集为空。

请注意“综合候选”的特定概念。这是“颠倒论点”的标准说法。

本节的其余部分详细说明了如果选择了一个重写的候选者意味着什么(又名:如何综合调用)。要找出哪个候选人被选中,我们必须深入研究 C++ 标准中最恐怖的章节中最恐怖的部分:

最佳可行功能匹配

这里重要的是这个声明:

如果对于所有参数 i,ICSi(F1) 不是比 ICSi(F2) 更差的转换序列,则一个可行函数F1被定义为比另一个可行函数更好的函数F2,然后

这很重要……因为this. 字面上地。

根据[over.ics.scs]的规则,身份转换比添加限定符的转换更匹配。

A{}是纯右值,而且...不是constthis成员函数的参数也不是。所以这是一个恒等转换,这是一个比const A&非成员函数的转换序列更好的转换序列。

是的,有一条规则进一步明确地使候选列表中的重写函数不那么可行。但这并不重要,因为重写的调用更适合单独使用函数参数。

如果您使用显式变量并像这样声明一个A const a{};,那么[over.match.best]/2.8会参与进来并降低重写版本的优先级。如这里所见。同样,如果你制作成员函数const,你也会得到一致的行为

  • @cigien:编译器不合成函数。它合成*表达式*,然后解析为在重载解析期间考虑的一组重载函数。只要看看我从标准中引用的文字就可以了解它是如何工作的。 (2认同)