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

问题描述

在C ++ 20中有一些关于重写比较运算符的新规则,我正在尝试了解它们的工作方式。我遇到了以下program

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

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

解决方法

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

可以通过一个相对简单的示例来说明倒车方面。

如果没有特定的B::operator==(A)来处理b == a,则可以使用相反的方法来进行处理:A::operator==(B)。这是有道理的,因为平等是双向关系:(a == b) => (b == a)

另一方面,对

次要运算符的重写涉及使用不同运算符。考虑a > b。如果您找不到直接执行此操作的功能,例如A::operator>(B),则该语言将开始寻找A::operator<=>(B)之类的东西,然后简单地从中计算结果。

这是一个简单的过程视图,但是我的大多数学生似乎都知道。如果您想了解更多详细信息,请参见C ++ 20的[over.match.oper]部分,这是重载分辨率的一部分(@是运算符的占位符):

对于关系运算符和相等运算符,重写的候选者包括运算符<=>的所有成员,非成员和内置候选者,对于这些运算符,重写表达式(x <=> y) @ 0使用operator<=>

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


从此过去了,必须提供真实的operator==operator<,然后进行锅炉电镀的日子:

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

如果我弄错了一个或多个错误,请不要抱怨,这只说明了我对C ++ 20更好的看法,因为您现在只需要提供最少的一组(很可能是{{ 1}}以及其他任何需要提高效率的内容),然后让编译器来照顾它:-)


可以通过以下代码辨别为什么一个人比另一个人被选中的问题:

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 } 的{​​{1}}的性质决定了哪个更好。

顺便说一句,您可能想看看this article,它提供了更深入的了解。

,

从非语言律师的角度来看,它的工作原理是这样的。 C ++ 20要求operator==计算两个对象是否相等。相等的概念是可交换的:如果A == B,则B ==A。因此,如果C ++ 20的参数反转规则可以调用两个operator==函数,则您的代码应具有完全相同的任何一种方式。

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


因此,让我们进入细节。所谓“细节”,是指标准中最恐怖的一章:函数重载解析。

[over.match.oper]/3定义了用于为操作员重载构建候选功能集的机制。 C ++ 20通过引入"rewritten candidates"来添加此功能:一组候选函数,这些候选函数是通过以C ++ 20认为在逻辑上等效的方式重写表达式而发现的。这仅适用于关系和不等式运算符。

该集合的构建符合以下条件:

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

请注意“综合候选人”的特定概念。这是“反转参数”的标准说法。

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

Best viable function matching

这是重要的声明:

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

那很重要,因为this。从字面上看。

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

A{}是一个prvalue,而...不是const。成员函数的this参数也不是。因此,这是一个身份转换,这比去往非成员函数const A&的转换顺序要好。

是的,还有一条规则可以更明确地使候选列表中的重写函数不可行。但这无关紧要,因为重写的调用与 alone 函数参数更匹配。

如果您使用显式变量并像这样A const a{};声明一个变量,则[over.match.best]/2.8会介入并取消优先于重写的版本。 As seen here.同样,如果您使成员函数const,您也get consistent behavior