What is the <=> ("spaceship", three-way comparison) operator in C++?

C++OperatorsC++ FaqC++20Spaceship Operator

C++ Problem Overview


While I was trying to learn about C++ operators, I stumbled upon a strange comparison operator on cppreference.com,* in a table that looked like this:

enter image description here

"Well, if these are common operators in C++, I better learn them", I thought. But all my attempts to elucidate this mystery were unsuccessful. Even here, on Stack Overflow I had no luck in my search.

Is there a connection between <=> and C++?

And if there is, what does this operator do exactly?

* In the meantime cppreference.com updated that page and now contains information about the<=>operator.

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

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, each having 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.

Solution 3 - C++

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.

Solution 4 - C++

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: https://stackoverflow.com/questions/32816843/are-any-c-operator-overloads-provided-automatically-based-on-others

Tested on Ubuntu 20.04, GCC 10.2.0.

Solution 5 - C++

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionq-l-pView Question on Stackoverflow
Solution 1 - C++mscView Answer on Stackoverflow
Solution 2 - C++q-l-pView Answer on Stackoverflow
Solution 3 - C++Stig HemmerView Answer on Stackoverflow
Solution 4 - C++Ciro Santilli Путлер Капут 六四事View Answer on Stackoverflow
Solution 5 - C++Nithin RView Answer on Stackoverflow