【问题标题】:What is the <=> ("spaceship", three-way comparison) operator in C++?什么是 C++ 中的 <=>(“宇宙飞船”,三向比较)运算符?
【发布时间】:2018-05-08 01:27:14
【问题描述】:

当我试图了解 C++ 运算符时,我在cppreference.com,* 上偶然发现了一个奇怪的比较运算符,看起来像这样:

“好吧,如果这些是 C++ 中的常用运算符,我最好学习它们”,我想。但我所有试图解开这个谜团的尝试都没有成功。即使在这里,在 Stack Overflow 上我的搜索也没有运气。

C++之间有联系吗?

如果有,这个操作符具体是做什么的?

* 与此同时,cppreference.com 更新了该页面,现在包含有关&lt;=&gt;operator 的信息。

【问题讨论】:

  • @cubuspl42 bar&lt; foo::operator&lt;=&gt; 是一个类似于&lt;-- 运算符的示例。
  • @hacks:对。 Like C++11 是关于实现 C++11 的编译器的标签。而 C++14 是关于实现 C++14 的编译器的标签。 C++17 是关于实现 C++17 的编译器。不,C++20 是关于 C++20 的标签。由于这个问题是关于 C++20 的,所以它就在那里。错误的标签 wiki,而不是标签本身。

标签: c++ operators c++-faq c++20 spaceship-operator


【解决方案1】:

这称为三路比较运算符。

根据P0515论文提案:

有一个新的三向比较运算符 &lt;=&gt;。表达式 a &lt;=&gt; b 返回一个对象,如果 a &lt; b 比较 &lt;0,如果 a &gt; b 比较 &gt;0,如果 ab 相等/等价,则比较 ==0

要为您的类型编写所有比较,只需写operator&lt;=&gt; 返回适当的类别类型:

  • 如果您的类型自然支持&lt;,则返回一个_ordering,我们将有效地生成&lt;&gt;&lt;=&gt;===!=; 否则返回 一个 _equality,我们将有效地生成 ==!=

  • 如果你的类型 a == b 暗示 f(a) == f(b) 则返回强(可替换性,其中 f 仅读取比较显着状态 使用非私有 const 接口访问),否则返回 弱。

cppreference 说:

三路比较运算符表达式具有以下形式

lhs <=> rhs   (1)  

表达式返回一个对象

  • 比较&lt;0 如果lhs &lt; rhs
  • 比较&gt;0 如果lhs &gt; rhs
  • 如果lhsrhs 相等/等价,则比较==0

【讨论】:

  • 对于那些对“比较&lt;0”、“比较&gt;0”和“比较==0”的含义感到困惑的人(就像我一样),他们的意思是&lt;=&gt;返回负值、正值或零值,具体取决于参数。很像strncmpmemcmp
  • @Dai 尽管'a' &lt; 'a''c' &lt; 'a' 都是假的,'a' &lt; 'a''a' &lt; 'c' 都不是。在强排序以下是正确的:a != ba &lt; b || b &lt; a
  • @Revolver_Ocelot 啊,所以它可以定义/生成为operator==(T x, T y) { return !(x &lt; y) &amp;&amp; !(y &lt; x); }operator!=(T x, T y) { return (x &lt; y) || (y &lt; x); } - 啊哈!当然,这比真正的 == 效率低,因为它调用了两次比较,但仍然很整洁。
  • “返强”和“返弱”是什么意思?
  • @hkBattousai 表示对象返回,比较 &lt; 0 评估为真。也就是说,如果a &lt; b(a &lt;=&gt; b) &lt; 0 始终为真。
【解决方案2】:

2017-11-11,ISO C++ 委员会采纳了Herb Sutter 关于“宇宙飞船”三路比较运算符的提议,作为添加到C++20。在题为Consistent comparison Sutter 的论文中,Maurer 和 Brown 展示了新设计的概念。有关该提案的概述,请参阅文章摘录:

表达式 a b 返回一个比较 if a ,如果 a > b 比较 >0,如果 a 和 b 是比较 ==0 相等/等价。

常见情况:要编写类型 X 与类型 Y 的所有比较,使用成员语义,只需编写:

auto X::operator<=>(const Y&) =default;

高级案例:要编写类型 X 与类型 Y 的所有比较,只需编写 operator 采用 Y,可以使用 =default 在需要时获取成员语义,并返回 适当的类别类型:

  • 如果您的类型自然支持 ,则返回 _ordering,我们将有效地生成对称 > >、>===!=;否则返回 _equality,我们将有效地生成 对称 ==!=
  • 如果您的类型 a == b 意味着 f(a) == f(b)(可替代性,其中 f 只读取比较显着状态 可以使用公共 const 成员访问),否则返回 弱_

比较类别

五个比较类别被定义为std:: 类型,每个都有以下预定义值:

+--------------------------------------------------------------------+
|                  |          Numeric  values          | Non-numeric |
|     Category     +-----------------------------------+             |
|                  | -1   | 0          | +1            |   values    |
+------------------+------+------------+---------------+-------------+
| strong_ordering  | less | equal      | greater       |             |
| weak_ordering    | less | equivalent | greater       |             |
| partial_ordering | less | equivalent | greater       | unordered   |
| strong_equality  |      | equal      | nonequal      |             |
| weak_equality    |      | equivalent | nonequivalent |             |
+------------------+------+------------+---------------+-------------+

这些类型之间的隐式转换定义如下:

  • strong_ordering 的值 {less, equal, greater} 隐式转换为:
    • weak_ordering 的值为 {less, equivalent, greater}
    • partial_ordering 的值为 {less, equivalent, greater}
    • strong_equality 的值为 {unequal, equal, unequal}
    • weak_equality 的值为 {nonequivalent, equivalent, nonequivalent}
  • weak_ordering 的值 {less, equivalent, greater} 隐式转换为:
    • partial_ordering 的值为 {less, equivalent, greater}
    • weak_equality 的值为 {nonequivalent, equivalent, nonequivalent}
  • partial_ordering 的值 {less, equivalent, greater, unordered} 隐式转换为:
    • weak_equality 带有值 {nonequivalent, equivalent, nonequivalent, nonequivalent}
  • strong_equality 的值为 {equal, unequal} 隐式转换为:
    • weak_equality 带有值 {equivalent, nonequivalent}

三路比较

引入了&lt;=&gt;token。旧源代码中的字符序列&lt;=&gt;tokenizes 为&lt;= &gt;。例如X&lt;&amp;Y::operator&lt;=&gt;需要添加一个空格来保留其含义。

可重载运算符&lt;=&gt;是三路比较函数,优先级高于&lt;,低于&lt;&lt;。它返回一个可以与literal0比较的类型,但允许其他返回类型,例如支持表达式模板。在语言和标准库中定义的所有&lt;=&gt;operators 返回上述 5 种std::comparison 类别类型之一。

对于语言类型,提供了以下内置&lt;=&gt;same-type 比较。除非另有说明,否则都是 constexpr。不能使用标量提升/转换异构调用这些比较。

  • 对于bool、整数和指针类型,&lt;=&gt;returnsstrong_ordering
  • 对于指针类型,不同的 cv-qualifications 和derived-to-base 转换允许调用同构的内置&lt;=&gt;,并且有内置的异构operator&lt;=&gt;(T*, nullptr_t)。只有指向同一对象/分配的指针的比较才是常量表达式。
  • 对于基本浮点类型,&lt;=&gt; 返回partial_ordering,并且可以通过将参数扩展为更大的浮点类型来异构调用。
  • 对于枚举,&lt;=&gt; 返回的值与枚举的基础类型&lt;=&gt; 相同。
  • 对于nullptr_t,&lt;=&gt; 返回strong_ordering并且总是让equal
  • 对于可复制数组,T[N] &lt;=&gt; T[N]返回与T's&lt;=&gt;相同的类型,并执行字典元素比较。其他数组没有&lt;=&gt;
  • 对于void没有&lt;=&gt;

为了更好地理解这个运算符的内部工作原理,请阅读原文paper。这正是我使用搜索引擎发现的。

【讨论】:

  • 好像 cpp 还不够复杂。为什么不简单地写一个比较方法...
  • @Leandro 宇宙飞船算子就是那个比较方法。此外,它还可以正常工作并写入(或删除)其他六个比较运算符。我将采用一个在六个单独的样板中编写的比较运算符函数。
  • 请注意 _equality 类型已死:事实证明 &lt;=&gt; 与四个关系运算符配合得很好,但与两个相等运算符配合得不好(尽管有一些强大的语法糖来支持您想要所有这些的常见情况)。
【解决方案3】:

由于引用的网页已更改,此答案已变得无关紧要

web page you are referencing 坏了。那天编辑了很多,不同的部分不同步。我看的时候的状态是:

在页面顶部,它列出了当前存在的比较运算符(在 C++14 中)。那里没有&lt;=&gt;

在页面底部,他们应该列出相同的运营商,但他们搞砸了并添加了这个未来的建议。

gcc 还不知道&lt;=&gt; (而-std=c++14 永远不会知道),所以它认为你的意思是a &lt;= &gt; b。这解释了错误消息。

如果您在五年后尝试同样的事情,您可能会收到更好的错误消息,例如&lt;=&gt; not part of C++14.

【讨论】:

  • OP 链接到的网页是正确的,您链接到的单独页面也是正确的。它用(C++20 起)标签限定&lt;=&gt; 运算符,告诉您期望它在哪个标准版本中。标准标签是cppreference.com 遵循的约定。当然,您没有在时间机器中返回的编译器为您提供支持,但 cpprefernce 会(正确地)告诉您会发生什么。
  • 是的,但是...不是答案。你在评论……什么的。
  • 我打算链接到与问题相同的网页,但错过了。我想我回答了其他答案没有回答的部分问题。我忽略了主要的粗体问题,因为其他人已经回答了。
【解决方案4】:

默认&lt;=&gt;会自动免费提供==, !=, &lt;, &gt;, &lt;=, &gt;=

C++20 有一个新的“默认比较”功能设置,因此默认&lt;=&gt; 可以免费提供所有其他功能。我相信这是添加operator&lt;=&gt;的主要动机。

改编自https://en.cppreference.com/w/cpp/language/default_comparisons

main.cpp

#include <cassert>
#include <compare>
#include <set>

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

int main() {
    Point pt1{1, 1}, pt2{1, 2};

    // Just to show it Is enough for `std::set`.
    std::set<Point> s;
    s.insert(pt1);

    // All of these are automatically defined for us!
    assert(!(pt1 == pt2));
    assert( (pt1 != pt2));
    assert( (pt1 <  pt2));
    assert( (pt1 <= pt2));
    assert(!(pt1 >  pt2));
    assert(!(pt1 >= pt2));
}

编译运行:

sudo apt install g++-10
g++-10 -ggdb3 -O0 -std=c++20 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

上面的等效更明确的版本是:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point& other) const {
        if (x < other.x) return -1;
        if (x > other.x) return 1;
        if (y < other.y) return -1;
        if (y > other.y) return 1;
        return 0;
    }
    bool operator==(const Point& other) const = default;
};

在这种情况下,我们需要显式设置bool operator==(const Point&amp; other) const = default;,因为如果operator&lt;=&gt; 没有默认设置(例如,如上所述),那么operator== 不会自动默认设置:

根据任何operator&lt;=&gt; 重载的规则,默认的&lt;=&gt; 重载还允许将类型与&lt;&lt;=&gt;&gt;= 进行比较。

如果operator&lt;=&gt; 是默认的,而operator== 根本没有声明,那么operator== 是隐式默认的。

上面的例子使用了与默认operator&lt;=&gt;相同的算法,cppreference 解释为:

默认的operator&lt;=&gt;通过依次比较T的基(从左到右深度优先)和非静态成员(按声明顺序)子对象来进行字典比较以计算,递归扩展数组成员(按下标递增的顺序),当发现不相等的结果时提前停止

在 C++20 之前,您不能执行类似 operator== = default 的操作,并且定义一个运算符不会导致定义其他运算符,例如以下无法使用-std=c++17 编译:

#include <cassert>

struct Point {
    int x;
    int y;
    auto operator==(const Point& other) const {
        return x == other.x && y == other.y;
    };
};

int main() {
    Point pt1{1, 1}, pt2{1, 2};

    // Do some checks.
    assert(!(pt1 == pt2));
    assert( (pt1 != pt2));
}

有错误:

main.cpp:16:18: error: no match for ‘operator!=’ (operand types are ‘Point’ and ‘Point’)
   16 |     assert( (pt1 != pt2));
      |              ~~~ ^~ ~~~
      |              |      |
      |              Point  Point

上面的代码确实在-std=c++20下编译。

相关:Are any C++ operator overloads provided automatically based on others?

在 Ubuntu 20.04、GCC 10.2.0 上测试。

【讨论】:

  • 当不默认&lt;=&gt; 时,它是否也应该给==?它碰巧对我来说超载了&gt;&lt;,但是大喊== 运算符丢失了......默认情况下没有问题。
  • @TonyTannous 这似乎是根据en.cppreference.com/w/cpp/language/default_comparisons 的预期行为“根据任何运算符 重载的规则,默认的 重载也将允许将类型与 和 >=。如果 operator 是默认的,并且 operator== 根本没有声明,则 operator== 是隐式默认的。"解决方案是在我的示例之一中添加bool operator==(const Point&amp; other) const = default;
  • 是的,我还在p1185r2找到了这个后面的motivation
【解决方案5】:

C++ 20 中引入了三路比较运算符 ()。

这个表达式返回如下对象;

auto cmp  = a <=> b;

cmp > 0 if a > b
cmp = 0 if a == b
cmp < 0 if a < b  

示例程序

#include <iostream>

using namespace std;

int main()
{
        int lhs = 10, rhs = 20;
        auto result = lhs <=> rhs;

        if (result < 0) {
                cout << "lhs is less than rhs" << endl;
        }
        else if (result > 0) {
                cout << "lhs is greater than rhs" << endl;
        }
        else {
                cout << "lhs and rhs are equal" << endl;
        }

}

如何编译运行?

g++-10 threewaycmp.cpp -std=c++20
./a.out

结果

lhs is less than rhs

更多详情请参考以下链接 https://en.cppreference.com/w/cpp/language/operator_comparison

【讨论】:

    猜你喜欢
    • 2021-08-07
    • 2010-10-24
    • 2015-06-10
    • 2015-08-02
    • 2015-08-02
    • 2011-03-04
    • 1970-01-01
    • 2021-07-27
    相关资源
    最近更新 更多