295

While I was trying to learn about C++ operators, I stumbled upon the following table that listed a strange comparison operator. What does this <=> operator do?

2017 image of a table from cppreference.com Since 2017 cppreference.com updated that page and now contains detailed information about the<=>operator.

q-l-p
  • 4,304
  • 3
  • 16
  • 36
  • 1
    @cubuspl42 `bar< foo::operator<=>` is an example of how it could be like the `<--` operator. – Yakk - Adam Nevraumont Nov 24 '17 at 20:56
  • 10
    @haccks: Right. Like C++11 is a tag about compilers that implement C++11. And C++14 is a tag about compilers that implement C++14. And C++17 is about compilers that implement C++17. No, the C++20 is the tag for stuff about C++20. And since this question is about C++20, there it is. The tag wiki that was wrong, not the tag itself. – Nicol Bolas Nov 25 '17 at 01:03

6 Answers6

220

This is called the three-way comparison operator.

According to the P0515 paper proposal:

There’s a new three-way comparison operator, <=>. The expression a <=> b returns an object that compares <0 if a < b, compares >0 if a > b, and compares ==0 if a and b are equal/equivalent.

To write all comparisons for your type, just write operator<=> that returns the appropriate category type:

  • Return an _ordering if your type naturally supports <, and we’ll efficiently generate <, >, <=, >=, ==, and !=; otherwise return an _equality, and we’ll efficiently generate == and !=.

  • Return strong if for your type a == b implies f(a) == f(b) (substitutability, where f reads only comparison-salient state accessible using the nonprivate const interface), otherwise return weak.

The cppreference says:

The three-way comparison operator expressions have the form

lhs <=> rhs   (1)  

The expression returns an object that

  • compares <0 if lhs < rhs
  • compares >0 if lhs > rhs
  • and compares ==0 if lhs and rhs are equal/equivalent.
msc
  • 33,420
  • 29
  • 119
  • 214
  • 183
    For those who are confused (like I was) about what "compares `<0`", "compares `>0`", and "compares `==0`" means, they mean the `<=>` returns a negative, positive, or zero value, depending on the arguments. Much like `strncmp` and `memcmp`. – Cornstalks Nov 24 '17 at 15:29
  • The first bullet-point says that if `<` is defined then the compiler will generate `==` - but how is that possible? `'a' < 'b' == 1`, but both `'a' < 'a'` and `'c' < 'a'` are both `== 0` so it shouldn't be possible to derive a comparison to be used by `==` without further information. – Dai Nov 26 '17 at 06:59
  • 1
    @Dai even though both `'a' < 'a'` and `'c' < 'a'` are both false, `'a' < 'a'` and `'a' < 'c'` are not. IN strong ordering following is true: `a != b` → `a < b || b < a` – Revolver_Ocelot Nov 26 '17 at 07:12
  • 1
    @Revolver_Ocelot Ah, so it can be defined/generated as `operator==(T x, T y) { return !(x < y) && !(y < x); }` and `operator!=(T x, T y) { return (x < y) || (y < x); }` - ah-ha! Of course this is less efficient than a true `==` as it invokes the comparison twice, but still neat. – Dai Nov 26 '17 at 07:24
  • 5
    What do "return strong" and "return weak" mean? – lucidbrot Nov 26 '17 at 12:24
  • What does `compares <0` mean? Can you give a use-case example? – hkBattousai Nov 28 '17 at 15:11
  • 2
    @hkBattousai it means that the object returns, when compared `< 0` evaluates to true. That is, if `a < b` then `(a <=> b) < 0` is always true. – rmobis Nov 28 '17 at 18:53
  • 1
    @hkBattousai I had the same question! https://stackoverflow.com/questions/47498486/what-does-compares-0-mean – q-l-p Nov 29 '17 at 04:19
  • 1
    So `a <=> 0` is the sign function? – kepe Nov 20 '18 at 23:01
  • 1
    @lucidbrot it is not "return strong" or "return weak". It is "strong ordering" and "weak ordering". OT, but the former means a "transitive none-reflective relation", while the later means "transitive reflective relation". Refer to "relation" in modern algebra. – Red.Wave Mar 01 '19 at 08:52
156

On 2017-11-11, the ISO C++ committee adopted Herb Sutter's proposal for the <=> "spaceship" three-way comparison operator as one of the new features that were added to C++20. In the paper titled Consistent comparison Sutter, Maurer and Brown demonstrate the concepts of the new design. For an overview of the proposal, here's an excerpt from the article:

The expression a <=> b returns an object that compares <0 if a < b, compares >0 if a > b, and compares ==0 if a and b are equal/equivalent.

Common case: To write all comparisons for your type X with type Y, with memberwise semantics, just write:

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

Advanced cases: To write all comparisons for your type X with type Y, just write operator<=> that takes a Y, can use =default to get memberwise semantics if desired, and returns the appropriate category type:

  • Return an _ordering if your type naturally supports <, and we’ll efficiently generate symmetric <, >, <=, >=, ==, and !=; otherwise return an _equality, and we’ll efficiently generate symmetric == and !=.
  • Return strong_ if for your type a == b implies f(a) == f(b) (substitutability, where f reads only comparison-salient state that is accessible using the public const members), otherwise return weak_.
Comparison Categories

Five comparison categories are defined as std:: types, with the following predefined values:

┌──────────────────┬───────────────────────────────────┬─────────────┐
│                  │          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 │             │
└──────────────────┴──────┴────────────┴───────────────┴─────────────┘

Implicit conversions between these types are defined as follows:

  • strong_ordering with values {less, equal, greater} implicitly converts to:
    • weak_ordering with values {less, equivalent, greater}
    • partial_ordering with values {less, equivalent, greater}
    • strong_equality with values {unequal, equal, unequal}
    • weak_equality with values {nonequivalent, equivalent, nonequivalent}
  • weak_ordering with values {less, equivalent, greater} implicitly converts to:
    • partial_ordering with values {less, equivalent, greater}
    • weak_equality with values {nonequivalent, equivalent, nonequivalent}
  • partial_ordering with values {less, equivalent, greater, unordered} implicitly converts to:
    • weak_equality with values {nonequivalent, equivalent, nonequivalent, nonequivalent}
  • strong_equality with values {equal, unequal} implicitly converts to:
    • weak_equality with values {equivalent, nonequivalent}
Three-way comparison

The<=>token is introduced. The character sequence<=>tokenizes to<= >, in old source code. For example,X<&Y::operator<=>needs to add a space to retain its meaning.

The overloadable operator<=>is a three-way comparison function and has precedence higher than< and lower than<<. It returns a type that can be compared against literal0but other return types are allowed such as to support expression templates. All<=>operators defined in the language and in the standard library return one of the 5 aforementionedstd::comparison category types.

For language types, the following built-in<=>same-type comparisons are provided. All are constexpr, except where noted otherwise. These comparisons cannot be invoked heterogeneously using scalar promotions/conversions.

  • Forbool, integral, and pointer types,<=>returnsstrong_ordering.
  • For pointer types, the different cv-qualifications and derived-to-base conversions are allowed to invoke a homogeneous built-in<=>, and there are built-in heterogeneousoperator<=>(T*, nullptr_t). Only comparisons of pointers to the same object/allocation are constant expressions.
  • For fundamental floating point types,<=> returnspartial_ordering, and can be invoked heterogeneously by widening arguments to a larger floating point type.
  • For enumerations,<=> returns the same as the enumeration's underlying type's<=>.
  • Fornullptr_t,<=> returnsstrong_orderingand always yieldsequal.
  • For copyable arrays,T[N] <=> T[N]returns the same type asT's<=>and performs lexicographical elementwise comparison. There is no<=>for other arrays.
  • Forvoidthere is no<=>.

To better understand the inner workings of this operator, please read the original paper. This is just what I've found out using search engines.

q-l-p
  • 4,304
  • 3
  • 16
  • 36
  • 5
    As if cpp wasn't complex enough already. Why not simply write a comparison method... – Leandro Feb 24 '19 at 10:13
  • 14
    @Leandro The spaceship operator *is* that comparison method. Additionally, it Just Works and writes (or deletes) the six other comparison operators. I’ll take one comparison operator function written over six individual boilerplates. – jonspaceharper Mar 23 '19 at 17:13
  • 3
    Note that the `_equality` types died: it turned out that `<=>` plays well with the four relational operators but not as well with the two equality operators (although there’s some intense syntactic sugar to support the common case where you want all of them). – Davis Herring Feb 10 '20 at 17:24
23

Defaulting <=> automatically gives ==, !=, <, >, <=, >= for free

C++20 has a new "default comparison" feature setup so that defaulting <=> gives all the others for free. I believe that this has been the major motivation behind the addition of operator<=>.

Adapted from 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));
}

compile and run:

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

An equivalent more explicit version of the above would be:

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

In this case, we need to explicitly set bool operator==(const Point& other) const = default; because if operator<=> is not defaulted (e.g. as given explicitly above), then operator== is not automatically defaulted:

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.

The above example uses the same algorithm as the default operator<=>, as explained by cppreference as:

The default operator<=> performs lexicographical comparison by successively comparing the base (left-to-right depth-first) and then non-static member (in declaration order) subobjects of T to compute <=>, recursively expanding array members (in order of increasing subscript), and stopping early when a not-equal result is found

Before C++20, you could not do something like operator== = default, and defining one operator would not lead to the others being defined, e.g. the following fails to compile with -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));
}

with error:

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

The above does compile under -std=c++20 however.

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

Tested on Ubuntu 20.04, GCC 10.2.0.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 1
    When not defaulting `<=>` should it give `==` as well? it happen to overload `>` and `<` for me, but yells `==` operator is missing... when defaulting there's not issue. – Tony Tannous Nov 15 '20 at 13:05
  • 1
    @TonyTannous that seems to be the expected behaviour according to https://en.cppreference.com/w/cpp/language/default_comparisons "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." The solution is to add `bool operator==(const Point& other) const = default;` as in one of my examples. – Ciro Santilli OurBigBook.com Nov 15 '20 at 13:09
  • Yes, I also found the [motivation](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1185r2.html#motivation) behind this in [p1185r2](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1185r2.html) – Tony Tannous Nov 19 '20 at 12:08
22

Three-Way Comparison operator (<=>) is introduced in C++ 20.

This expression returns the object as below;

auto cmp  = a <=> b;

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

Example Program

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

}

How to compile and run?

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

Result

lhs is less than rhs

Please refer to below link for more details https://en.cppreference.com/w/cpp/language/operator_comparison

Nithin R
  • 761
  • 6
  • 6
12

This answer has become irrelevant since the referenced web page has changed

The web page you are referencing was broken. It was being edited a lot that day and different parts were not in sync. The status when I was looking at it was:

At the top of the page it lists the currently existing comparison operators (in C++14). There is no <=> there.

At the bottom of the page, they should have listed the same operators, but they goofed and added this future suggestion.

gcc doesn't know about <=> yet (and with -std=c++14, never will), so it thinks you meant a <= > b. This explains the error message.

If you try the same thing five years from now you will probably get a better error message, something like <=> not part of C++14.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Stig Hemmer
  • 2,604
  • 2
  • 11
  • 17
  • 1
    The web page OP links to is correct, as is the separate page you link to. It qualifies the `<=>` operator with the (since C++20) label, telling you which version of the standard to expect it in. Standards labeling is a convention that cppreference.com follows. Of course you don't have a compiler that came back in a time machine to support it for you, but cpprefernce tells you (correctly) what to expect. – Spencer Nov 24 '17 at 22:13
  • Yes, but... Not an answer. You are commenting... or something. – q-l-p Nov 24 '17 at 22:34
  • 2
    I intended to link to the same web page as the question, but missed. I think I answered the parts of the question other answers didn't. I ignored the main bolded question since other had already answered that. – Stig Hemmer Nov 27 '17 at 08:25
1

The virtue of <=> is with complex types whose comparison is expensive, for situations like tree navigation. You can "int x = A <=> B;" once and then determine >, <, >=, <=, ==, != from x without additional comparisons of A and B. For a tree, imagine bool find(root, A){ if root is nullptr return false; int x = A <=> root->B; if !x, you found it return true, else if x < 0, find left, else find right}. (Tune for tail recursion.)

I thought some language had a three way control flow for this, like switch, but it is hard to recall via google. It's a situation going back to C strcmp(), memcmp(), continuing with JAVA compareTo(). Crowdsource me on this!