当“运算符 <=>”就足够时,为什么我必须提供“运算符 ==”?

Why must I provide 'operator ==' when 'operator <=>' is enough?

提问人:xmllmx 提问时间:7/2/2021 最后编辑:cigienxmllmx 更新时间:7/5/2021 访问量:7555

问:

#include <compare>

struct A
{
    int n;

    auto operator<=>(A const& other) const
    {
        if (n < other.n)
        {
            return std::strong_ordering::less;
        }
        else if (n > other.n)
        {
            return std::strong_ordering::greater;
        }
        else
        {
            return std::strong_ordering::equal;
        }
    }

    // compile error if the following code is commented out.
    // bool operator==(A const& other) const
    // { return n == other.n; }
};

int main()
{   
    A{} == A{};
}

观看在线演示

为什么我必须提供什么时候足够?operator ==operator <=>

C++ 律师 C++20 语言设计 宇宙飞船操作员

评论

1赞 xmllmx 7/2/2021
为什么不包括?我的意思是,如果提供,请使用它;如果没有,请改用?为什么 C++ 标准不是这样设计的?<=>====<=>
5赞 jamesdlin 7/2/2021
open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1190r0.html
6赞 StoryTeller - Unslander Monica 7/2/2021
你知道的...我链接的第二个副本也是你问的......
1赞 Nicol Bolas 7/2/2021
@HansOlsson:你不能从他们下面改变人们代码的含义和行为。此外,您不能根据返回类型进行重载,因此无法请求特定类型的排序。您只能使用类型提供的内容,并且已经依赖于标准库类型的现有排序运算符。你的建议是行不通的。
3赞 Justin 7/3/2021
这个问题是另一个问题的重复:stackoverflow.com/q/58780829/1896169,但我不想把它作为重复结束,因为这里的答案提供了不同的信息/不同的观点来帮助理解相同的信息......

答:

7赞 HolyBlackCat 7/2/2021 #1

因为有时实现得比使用 ,所以编译器默认拒绝使用潜在的次优实现。==a <=> b == 0

例如,考虑 ,它可以在循环元素之前检查大小是否相同。std::string

请注意,您不必手动实现。你可以,这将在 .===default<=>

另请注意,如果您自己,那么 ing 是没有必要的。=default<=>=default==

评论

1赞 einpoklum 7/2/2021
与@errorika相同的问题:正确、缓慢的代码应该是可编译的。我认为警告就足够了。
0赞 HolyBlackCat 7/3/2021
@einpoklum 该标准实际上没有警告的概念。它不能强制要求他们。
0赞 einpoklum 7/3/2021
尽管如此 - 我认为您的答案中没有理由要求 == 而不是推荐。
0赞 HolyBlackCat 7/3/2021
@einpoklum 你的问题是什么?在这种情况下,提交决定拒绝可能很慢的代码,所以这就是我们现在所拥有的。
0赞 einpoklum 7/3/2021
问题是为什么?C++很少(如果有的话)拒绝其他有效的代码,因为它可能不快。
3赞 J-16 SDiZ 7/2/2021 #2

不,你没有。 只需添加

  bool operator==(A const& other) const = default;

https://godbolt.org/z/v1WGhxca6

您始终可以将它们重载为不同的语义。为了防止意外的自动生成函数,需要= default

评论

0赞 maowtm 7/9/2021
这不会使用 .operator<=>
64赞 paxdiablo 7/2/2021 #3

为什么我必须提供什么时候足够?operator==operator<=>

嗯,主要是因为这还不够:-)

当 C++ 重写语句时,相等和排序是不同的桶:

平等 订购
主要 == <=>
二 次 != <, >, <=, >=

主算子具有反转的能力,而辅助算子具有根据其对应的主算子重写的能力:

  • 倒车意味着可以是:a == b
    • a.operator==(b)如果有的话;或
    • b.operator==(a)如果没有。
  • 重写意味着可以是:a != b
    • ! a.operator==(b)如果可用

如果您必须重写反转它,最后一个也可能是(我不完全确定,因为我的经验主要是比较相同的类型)。! b.operator==(a)

但是,默认情况下不跨相等/排序边界进行重写的要求意味着它不是 的重写候选者。<=>==


等式和有序这样分开的原因可以在这篇 P1185 论文中找到,来自讨论这个问题的众多标准会议之一。

在许多情况下,自动实现可能非常低效。字符串、向量、数组或任何其他集合浮现在脑海中。您可能不想用于检查两个字符串的相等性:==<=><=>

  • "xxxxx(a billion other x's)";和
  • "xxxxx(a billion other x's)_and_a_bit_more".

这是因为必须处理整个字符串才能确定排序,然后检查排序是否强相等。<=>

但是,预先进行简单的长度检查会很快告诉您它们不相等。这是O(n)时间复杂度(十亿左右的比较)和O(1)之间的差异,O(1)是一个近乎即时的结果。


如果你知道它没问题(或者你乐于接受它可能带来的任何性能打击),你总是可以默认相等。但人们认为最好不要让编译器为你做出这个决定。

更详细地说,请考虑以下完整程序:

#include <iostream>
#include <compare>

class xyzzy {
public:
    xyzzy(int data) : n(data) { }

    auto operator<=>(xyzzy const &other) const {
        // Could probably just use: 'return n <=> other.n;'
        // but this is from the OPs actual code, so I didn't
        // want to change it too much (formatting only).

        if (n < other.n) return std::strong_ordering::less;
        if (n > other.n) return std::strong_ordering::greater;
        return std::strong_ordering::equal;
    }

    //auto operator==(xyzzy const &other) const {
    //    return n == other.n;
    //}

    //bool operator==(xyzzy const &) const = default;

private:
    int n;
};

int main() {
    xyzzy twisty(3);
    xyzzy passages(3);

    if (twisty < passages) std::cout << "less\n";
    if (twisty == passages) std::cout << "equal\n";
}

它不会按原样编译,因为它需要 final 语句。但是你不必提供一个真正的(第一个注释掉的块),你可以告诉它使用默认值(第二个)。而且,在这种情况下,这可能是正确的决定,因为使用默认值不会对性能产生实际影响。operator==


请记住,仅当您显式提供三向比较运算符(当然,您使用 or )时,才需要提供相等运算符。如果两者都不提供,C++ 将同时提供这两个默认值。==!=

而且,即使你必须提供两个函数(其中一个可能是默认的),它仍然比以前要好,你必须显式地提供它们,比如:

  • a == b.
  • a < b.
  • a != b,定义为 。! (a == b)
  • a > b,定义为 。! (a < b || a == b)
  • a <= b,定义为 。a < b || a == b
  • a >= b,定义为 。! (a < b)

评论

16赞 paxdiablo 7/3/2021
感谢雅利安人,他用新的表格格式编辑了我的答案,非常感谢。我什至不知道这是一种选择(你会认为在这里呆了十多年的人知道这样的事情)。有了这些新发现的知识,我未来的答案将得到极大的改善。好吧,至少在格式上,如果不是内容:-)
5赞 justhalf 7/5/2021
公平地说,表格格式只有 7 个月大
11赞 eerorika 7/2/2021 #4

当“运算符 <=>”就足够时,为什么我必须提供“运算符 ==”?

因为它不会被使用。

如果您要使用默认的就足够了:

struct A
{
    int n;
    auto operator<=>(A const& other) const = default;
};

基本上,它可能比 并且在许多情况下这是一种选择。当您提供用户定义时,语言实现无法知道后者是否可以替换为前者,也无法知道后者是否不必要地效率低下。n == n(a <=> a) == std::strong_ordering::equal<=>

因此,如果您需要自定义三向比较,则需要自定义相等性比较。示例类不需要自定义三向比较,因此应使用默认比较。

评论

1赞 einpoklum 7/2/2021
这难道不是警告的理由吗?
0赞 eerorika 7/2/2021
@einpoklum 所有警告均由实施人员自行决定。如果没有匹配的重载 == 或默认的 <=>,则使用 == 将导致程序格式错误。语言实现必须诊断格式错误的程序,但不需要是错误。如果不是,则通常将其视为语言扩展。
0赞 einpoklum 7/2/2021
然后我不明白仅仅因为效率较低而强迫用户编写额外的代码是合理的。例如,C++ 不会强制用户编写移动构造函数,这通常会导致大量无用的复制。
0赞 eerorika 7/2/2021
@einpoklum 编译器将隐式生成一个移动构造函数,而无需编写一个构造函数。
4赞 paxdiablo 7/3/2021
@einpoklum:“土豆,土豆”在写的时候有点失去效果,看起来作者已经疯了:-)。也许“potayto,potarto”会更好。
7赞 Don Hosek 7/3/2021 #5

从前面的答案来看,没有人解决另一个问题:出于排序目的,两个对象可能是等价的,但并不相等。例如,我可能想在 Unicode NFC 规范化中对字符串进行排序,并将大小写折叠为小写,但对于相等性测试,我想验证字符串是否实际上相同,大小写很重要,甚至可能区分输入中的 é 和 ' + e。

是的,这是一个有点做作的例子,但它有助于说明,的定义不需要排序,因此您甚至不能依赖潜在的返回。不能假定默认返回是有效的实现。<=><=>std::strong_ordering::equal==<=>std::strong_ordering::equal

评论

6赞 HolyBlackCat 7/3/2021
如果它这样做,我会认为类型被破坏了。对我来说,这将是一个合理的默认实现(如果不是因为性能问题),因为您可以随时覆盖它。
2赞 paxdiablo 7/3/2021
是的,我不确定我是否可以信任使用不同排序和相等规则的类型。这可能会导致奇怪的情况,即对它们的集合进行排序可能会导致“相等”的事物彼此相距甚远:-)
4赞 alephzero 7/3/2021
@paxdiablo我猜你不“信任”IEEE-754格式的浮点数,然后:)
0赞 wizzwizz4 7/4/2021
@alephzero Rust 也不信任他们。
1赞 tchrist 7/4/2021
很棒的观察。关于Unicode排序规则算法的UTS#10在区分排序规则和比较以及解释为什么这很重要方面花费了一些麻烦,包括在A.3节确定性比较11.1节排序规则折叠中。