为什么我应该使用三路比较运算符(<=>)而不是双路比较运算符?这样做有什么优势吗?

14
#include <compare>
#include <iostream>

int main()
{ 
   auto comp1 = 1.1 <=> 2.2;
   auto comp2 = -1 <=> 1;
   std::cout << typeid(comp1).name()<<"\n"<<typeid(comp2).name();
}

输出:

struct std::partial_ordering
struct std::strong_ordering

我知道如果操作数具有整数类型,运算符将返回类型为std::strong_orderingPRvalue。我还知道如果操作数具有浮点类型,则运算符生成类型为std::partial_orderingPRvalue

但为什么我应该使用三路比较运算符而不是两路运算符(==!=<<=>>=)?这样做有哪些优势呢?


3
这是一篇由权威人士所写的文章,你可以在这里阅读它:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0515r2.pdf。 - anastaciu
3
我认为这取决于上下文。如果你正在创建一个类,实现 operator<=> (甚至在可能的情况下默认化)可以节省时间(开发和维护),而不必手动实现它们全部。另一方面,如果你想知道 if( 1.1 < 2.2 ),那么使用 <=> 可能没有太多意义。 - Ted Lyngmo
1
使用两个比较运算符“<=>”和“==”可以减少编写的代码量。相比编写六个比较运算符,出现错误/漏洞的机会更少。 - Eljay
1
你是在询问如何使用 <=> 进行比较,还是在一般情况下使用 <=> - Drew Dormann
@DrewDormann 实际上两者都是。 - east1000
4个回答

11

使用<=>操作符,可以通过一次比较确定排序


而其他操作符需要进行两次比较。

其他操作符的概要如下:

  • 如果a == b为假,则无法确定a < b还是a > b
  • 如果a != b为真,则无法确定a < b还是a > b
  • 如果a < b为假,则无法确定a == b还是a > b
  • 如果a > b为假,则无法确定a == b还是a < b
  • 如果a <= b为真,则无法确定a == b还是a < b
  • 如果a >= b为真,则无法确定a == b还是a > b

一个很好的副作用是,所有其他操作符都可以用<=>实现,编译器可以为您生成它们。

另一个副作用是,人们可能会因为在数学中使用<=>作为等价箭头而感到困惑,这几乎是自打打字机得到这些符号以来就一直如此。

(我个人对a <=> b仅在ab不相等时为“真值”感到非常恼火。)


11

对我来说,主要好处在于此运算符可以为类设置默认值,从而自动支持类的所有可能比较。也就是说,

#include <compare>

struct foo {
    int a;
    float b;
    auto operator<=>(const foo& ) const = default;
};

// Now all operations used before are defined for you automatically!

auto f1(const foo& l, const foo& r) {
    return l < r;
}

auto f2(const foo& l, const foo& r) {
    return l > r;
}

auto f3(const foo& l, const foo& r) {
    return l == r;
}

auto f4(const foo& l, const foo& r) {
    return l >= r;
}

auto f5(const foo& l, const foo& r) {
    return l <= r;
}

auto f6(const foo& l, const foo& r) {
    return l != r;
}

以前,所有这些操作都必须在类内部定义,这很繁琐且容易出错——因为每当向类中添加新成员时,就必须记得重新访问它们。


6

自行判断。

宇宙飞船操作符的主要目的不是专门用于比较对象。它的主要作用是允许编译器从宇宙飞船操作符中综合出其他比较运算符。

如果你不需要明确回答小于、等于或大于的问题,那么你就不需要直接调用它。使用对你有意义的运算符即可。

但如果你需要使一个类型可比较,你只需要写2个函数(宇宙飞船和相等性),而不需要写6个。而且,在编写这样的函数时,你可以在所涉及的各个类型上使用宇宙飞船操作符(如果它们能以这种方式进行比较)。这使得实现这样的函数变得更加容易。

宇宙飞船操作符允许你做的另一件有用的事情是告诉你比较将提供什么类型的排序方式。部分排序、强排序或其他类型。理论上,这可能很有用,但总体上并不常见。


4
如果将宇宙飞船设为“默认”,甚至不需要编写操作符“==”。 - Ted Lyngmo
1
你只需要编写两个函数(spaceship和equality)。我想知道是否也可以像其他关系运算符一样,基于宇宙飞船操作符来实现相等性。我一直以为是这样的。 - Scheff's Cat
4
只有在你使用= default时才是如此。需要显式定义比较运算符的类型通常有短路版本,如果你只关心相等性测试,则可以使用这些版本。因此,如果你自定义了其中一个,你就需要自定义另一个(你可以轻松地以太空船操作符的形式完成这个任务)。 - Nicol Bolas

5
“太空船”运算符是由Herb Sutter提出的,并被委员会采纳,将在C++20中实现。您可以在此查看详细报告,或者如果您更喜欢观看讲座,则在此观看该人亲自为其辩护的视频。在报告的第3/4页中,您可以看到主要用途:
需要在以下类中实现比较运算符,这是C++20之前所需的功能:
class Point
{
    int x;
    int y;

public:
    friend bool operator==(const Point &a, const Point &b) { return a.x == b.x && a.y == b.y; }
    friend bool operator<(const Point &a, const Point &b) { return a.x < b.x || (a.x == b.x && a.y < b.y); }
    friend bool operator!=(const Point &a, const Point &b) { return !(a == b); }
    friend bool operator<=(const Point &a, const Point &b) { return !(b < a); }
    friend bool operator>(const Point &a, const Point &b) { return b < a; }
    friend bool operator>=(const Point& a, const Point& b) { return !(a < b); }
    // ... non-comparisonfunctions ...
};

将被替换为:

class Point
{
    int x;
    int y;

public:
    auto operator<=>(const Point &) const = default; 
    // ... non-comparison functions ...
};

为了回答你的问题,将 operator<=> 作为类成员重载,使你能够使用所有比较运算符来比较类对象,而无需实现它们,如果没有声明,则默认情况下也默认了 operator==,这同时也实现了 operator!=,使得所有比较操作都可以用单个表达式实现。这是主要的使用情况。
我想指出,在 C++20 中引入 默认比较 功能之前,spaceship 运算符是不可能存在的。

Defaulted three-way comparison

[...]
Let R be the return type, each pair of subobjects a, b is compared as follows:
[...]
... if R is std::strong_ordering, the result is:

a == b ? R::equal : a < b ? R::less : R::greater

Otherwise, if R is std::weak_ordering, the result is:

a == b ? R::equivalent : a < b ? R::less : R::greater

Otherwise (R is std::partial_ordering), the result is:

a == b ? R::equal : a < b ? R::less : b < a ? R::greater : R::unordered

Per the rules for any operator<=> overload, a defaulted <=> overload will also allow the type to be compared with <, <=, >, and >=.

If operator<=> is defaulted and operator== is not declared at all, then operator== is implicitly defaulted.

另外,它也允许仅默认operator ==,这将实现operator !=,虽然不如前者灵活多样,但也是一个有趣的可能性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接