【问题标题】:Why must I provide 'operator ==' when 'operator <=>' is enough?当 'operator <=>' 足够时,为什么我必须提供 'operator =='?
【发布时间】:2021-09-14 03:53:50
【问题描述】:
#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 &lt;=&gt; 足够了?

【问题讨论】:

  • 为什么&lt;=&gt; 不包括==?我的意思是,如果提供了==,请使用它;如果没有,请改用&lt;=&gt;?为什么C++标准不是这样设计的?
  • 你知道......我链接的第二个副本也是你问的......
  • @HansOlsson:你无法改变人们代码的含义和行为。此外,您不能基于返回类型重载,因此无法请求 特定类型的排序。您只能使用该类型提供的内容,并且已经依赖于标准库类型的现有排序运算符。你的建议是行不通的。
  • 这个问题是另一个问题的副本:stackoverflow.com/q/58780829/1896169,但我不想将其作为副本关闭,因为这里的答案提供了不同的信息/不同的观点来帮助理解相同的信息...

标签: c++ language-lawyer c++20 language-design spaceship-operator


【解决方案1】:

operator&lt;=&gt; 足够了,为什么我必须提供operator==

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

在 C++ 重写您的语句时,平等和排序是不同的桶:

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

主算子可以取反,辅算子可以根据对应的主算子重写

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

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

但默认情况下不跨越相等/排序边界进行重写的要求意味着&lt;=&gt; 不是== 的重写候选者。


this P1185 paper 中可以找到平等和排序分开的原因,来自讨论此问题的众多标准会议之一。

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

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

这是因为&lt;=&gt; 必须处理整个 字符串来计算排序,然后检查排序是否强相等。

但预先进行简单的长度检查会非常快速告诉您它们是不相等的。这就是 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== 来作为最终语句。但是您不必提供 real 一个(第一个注释掉的块),您可以告诉它使用默认值(第二个)。而且,在这种情况下,这可能是正确的决定,因为使用默认值不会对性能产生真正的影响。


请记住,如果您显式提供三路比较运算符(当然您使用==!=),则只需要提供相等运算符。如果您都不提供,C++ 将为您提供两个默认值。

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

  • a == b.
  • a &lt; b
  • a != b,定义为! (a == b)
  • a &gt; b,定义为! (a &lt; b || a == b)
  • a &lt;= b,定义为a &lt; b || a == b
  • a &gt;= b,定义为! (a &lt; b)

【讨论】:

  • 致雅利安,他用新的表格格式编辑了我的答案,非常感谢。我什至不知道这是一个选择(你会认为已经在这里工作了十多年的人知道类似的事情)。这些新发现的知识将极大地改善我未来的答案。好吧,至少在格式方面,如果不是内容:-)
  • 为了公平起见,表格格式为only 7-month-old
【解决方案2】:

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

因为它不会被使用。

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

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

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

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

【讨论】:

  • 这不是只发出警告的理由吗?
  • @einpoklum 所有警告均由实施自行决定。如果没有匹配的重载 == 也没有默认的 ,那么使用 == 将导致程序格式错误。语言实现必须诊断格式错误的程序,但不一定是错误。如果不是,这通常被认为是语言扩展。
  • 那么我看不出有什么理由强迫用户编写额外的代码,因为否则效率较低。例如,C++ 不会强制用户编写移动构造函数,这通常会导致大量无用的复制。
  • @einpoklum 编译器将隐式生成移动构造函数,无需编写。
  • @einpoklum:“马铃薯,马铃薯”在写作时有点失去作用,看起来作者疯了:-)。也许“potayto, potarto”会更好。
【解决方案3】:

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

是的,这是一个有些人为的例子,但它表明&lt;=&gt; 的定义不需要strong 排序,因此您不能依赖&lt;=&gt;,甚至可能返回@ 987654326@。使== 默认为&lt;=&gt; 返回std::strong_ordering::equal 不能被认为是一个有效的实现。

【讨论】:

  • 如果它这样做,我会认为类型损坏。对我来说,这将是一个合理的默认实现(如果不是为了性能问题),因为您总是可以覆盖它。
  • 是的,我不确定我是否可以信任一个使用不同的排序和相等规则的类型。这可能会导致奇怪的情况,其中对它们的集合进行排序可能会导致“相等”的事物彼此相去甚远:-)
  • @paxdiablo 我猜你不“信任”IEEE-754格式的浮点数然后:)
  • @alephzero Rust 也不信任他们。
  • 伟大的观察。 UTS#10 on the Unicode Collation Algorithm 在区分排序规则和比较时遇到了一些麻烦,并解释了为什么这很重要,包括在 A.3 Deterministic Comparison 部分和 11.1 Collation Folding 部分中。
【解决方案4】:

因为== 的实现有时比使用a &lt;=&gt; b == 0 更快,所以编译器默认拒绝使用可能不是最佳的实现。

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

请注意,您不必手动实现==。你可以=default它,它将按照&lt;=&gt;来实现它。

另请注意,如果您是=default &lt;=&gt; 本身,则不需要=defaulting ==

【讨论】:

  • 与@errorika 相同的问题:正确,慢代码应该是可编译的。我认为一个警告就足够了。
  • @einpoklum 该标准并没有真正的警告概念。它不能强制他们。
  • 尽管如此 - 我认为您的回答中没有理由将 == 设为必需,而不是推荐。
  • @einpoklum 你有什么问题?在这种情况下,委员会决定拒绝可能很慢的代码,这就是我们现在所拥有的。
  • 问题是为什么? C++ 很少(如果有的话)拒绝其他有效代码,因为它可能不快。
【解决方案5】:

不,你没有。 只需添加

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

https://godbolt.org/z/v1WGhxca6

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

【讨论】:

  • 这不会使用operator&lt;=&gt;
猜你喜欢
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
  • 2011-04-23
  • 2012-04-09
  • 1970-01-01
  • 2023-02-02
  • 2016-06-09
相关资源
最近更新 更多