如何解决重写比较运算符会导致哪些重大变化?
在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。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。