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

问题描述

#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 <=> 就足够了?

解决方法

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

因为它不够 :-)

当 C++ 重写你的语句时,相等和顺序是不同的概念:

+-------------+------------+----------------+
|             |  Equality  |    Ordering    |
+-------------+------------+----------------+
|  Primary    |     ==     |      <=>       |
|  Secondary  |     !=     |  <,>,<=,>=  |
+-------------+------------+----------------+

主运算符具有反转的能力,二级运算符具有根据其对应的主运算符重写的能力。

通过反向,我的意思是 a == b 可以是 a.operator==(b)(如果可用)或 b.operator==(a)(如果不可用)。

通过重写,我的意思是 a > b 可以变为 a.operator<=>(b) > 0(如果可用)或 b.operator<=>(b) <= 0(如果不可用)。

从该表中可以看出,<=> 不是 == 的重写候选对象,因为它是一个排序操作而不是一个相等操作。


可以在讨论此问题的许多标准会议之一的this P1185 paper中找到将等式和排序分开的原因

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

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

那是因为 <=> 必须处理整个字符串来计算排序,然后检查排序是否强相等。

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


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

换句话说,请考虑以下完整程序:

#include <iostream>
#include <compare>

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

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

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

    //bool operator==(xyzzy const &that) 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== 作为最终语句。但是您不必提供真实(第一个评论块),您可以告诉它使用默认值(第二个)。

,

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

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

,

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

因为它不会被使用。

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

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

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

所以,如果你需要一个自定义的三向比较,那么你需要一个自定义的相等比较。示例类不需要自定义的三向比较,因此您应该使用默认的。

,

不,你没有。 只需添加

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

https://godbolt.org/z/v1WGhxca6

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

,

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

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

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

另请注意,如果您 =default <=> 本身,则不需要 =defaulting ==