53
#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 <=>

4

5 回答 5

62

为什么我必须提供足够的时间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) 之间的差异,一个近乎即时的结果。


如果你知道它会没问题,你总是可以默认相等(或者你很乐意忍受它可能带来的任何性能损失)。但是最好不要让编译器为您做出决定。

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

#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";
}

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


请记住,如果您明确提供三路比较运算符(并且您当然使用==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)
于 2021-07-02T07:20:38.357 回答
11

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

因为它不会被使用。

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

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

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

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

于 2021-07-02T07:22:12.347 回答
7

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

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

于 2021-07-02T17:12:49.497 回答
6

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

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

请注意,您不必==手动实施。你可以=default,它将根据<=>.

还要注意,如果你=default <=>自己,那么=defaulting==是没有必要的。

于 2021-07-02T07:19:42.410 回答
3

不,你没有。只需添加

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

https://godbolt.org/z/v1WGhxca6

您总是可以将它们重载到不同的语义。为了防止意外的自动生成功能, = default需要

于 2021-07-02T07:19:51.560 回答