为什么我必须提供 'operator ==',而 'operator <=>' 就足够了?

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

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

online demo

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

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

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

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

来实现它

另请注意,如果您 =default <=> 本身,则 =defaulting == 是没有必要的。

不,你不知道。 只需添加

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

https://godbolt.org/z/v1WGhxca6

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

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

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

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

Equality Ordering
Primary == <=>
Secondary != <, >, <=, >=

初级运算符可以被反转,二级运算符可以被重写根据其对应的初级运算符:

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

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

但是默认情况下不跨 equality/ordering 边界进行重写的要求意味着 <=> 不是 [=20= 的重写候选者].


原因为什么等式和排序如此分开可以在 this P1185 paper 中找到,来自讨论这个问题的许多标准会议之一。

在很多情况下,根据 <=> 自动实施 == 可能效率很低。想到字符串、向量、数组或任何其他 collections。您可能不想使用 <=> 来检查两个字符串的相等性:

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

那是因为 <=> 必须处理 整个 字符串才能计算出排序,然后检查排序是否为 strong-equal。

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


如果您知道它没问题(或者您乐于忍受它可能带来的任何性能损失),您始终可以默认 相等。但人们认为最好不要让编译器 为您做出 那个决定。

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

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

它不会编译 as-is 因为它需要一个 operator== 作为最终语句。但是你不必提供 real 一个(第一个 commented-out 块),你可以告诉它使用默认值(第二个)。而且,在这种情况下,这可能是正确的决定,因为使用默认设置不会对性能产生实际影响。


请记住,如果您 显式 提供 three-way 比较运算符(并且您使用 ==!=,当然)。如果两者都不提供,C++ 将为您提供这两个默认值。

而且,即使您必须提供两个功能(其中一个可能是默认功能),它仍然比以前更好,您必须明确提供它们 all,类似于:

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

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

因为不会用到

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

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

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

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

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

是的,这是一个有点做作的例子,但它表明 <=> 的定义不需要 strong 排序,所以你不能依赖<=> 甚至可能返回 std::strong_ordering::equal。将 == 默认设置为 <=> returns std::strong_ordering::equal 不能被认为是有效的实现。