Get this book -> Problems on Array: For Interviews and Competitive Programming

The three-way comparison operator `<=>`

, colloquially called the *spaceship operator* was added in C++20.

# Comparison

Comparison is one of the most commonly used operations in a program. Comparing the built-in types like `int`

's is defined by the language. However, comparison of user-defined types is provided by means of operator overloading.

When we compare objects `A`

and `B`

we are actually calling the operators with parameter. So something like `A<B`

might evaluate as `A.operator<(B)`

. Ofcourse the `operator<()`

has to be defined.

# Pre-C++20

C++ comparison operators include `==`

, `!=`

, `<`

, `<=`

, `>`

, and `>=`

. The first two are used to determine if two objects are equal or unequal, and the other four are used to order the objects.

For user-defined objects, use of an expression like `A@B`

where `@`

is any comparison operator, the overload resolution looks for the best matching candidate for this. First all the member functions, then all the non-member functions and finally all the built-in candidates are checked (in that order) and the best candidate among these is chosen.

Operators are usually defined in terms of others. One common idiom is to use the `operator<()`

to implement the other comparison operators and `operator==()`

to implement the `!=`

operator.

Use of `<`

to define the others may work for integers but poses problems for floating points, particularly the `NaN`

which must be `false`

for each one of these comparisons: `1.0f < NaN`

, `1.0 == NaN`

, `1.0 > NaN`

and also for `<=`

and `>=`

. But if `1.f <= NaN`

is `false`

, then `!(NaN < 1.f)`

is incorrectly determined to be `true`

.

Itโs recommended to write comparisons as non-member functions to support heterogeneous comparison. Comparisons like `a == 4`

and `4 == a`

should mean the same thing. To achieve this it is recommended to write comparisons as non-member functions and make them `friend`

s for convenience.

# A new operator <=>

The `operator<=>`

is the new ordering primitive. For two objects `A`

and `B`

, it determines if `A<B`

, `A==B`

or `A>B`

. It returns an object that compares `<0`

if `A<B`

, compares `==0`

if `A==B`

, and compares `>0`

if `A>B`

.

The comparison is thus three-ways and hence the name of the operator. C++ already has had such a function that compares three-way: basic_string compare() function that lexicographically compares two strings and returns a positive, 0 or a negative value.

The `operator<=>()`

returns an object that belongs to one of the comparison categories. The comparison categories are `strong_ordering`

, `weak_ordering`

and `partial_ordering`

.

For `strong_ordering`

, the values are `strong_ordering::less`

, `strong_ordering::equal`

and `strong_ordering::greater`

. The `strong_ordering::equal`

implies substitutability. i.e., if `(a <=> b) == strong_ordering::equal`

then for functions f, f(a) == f(b). The function usually is for equality of the value of the type such as *elements* of the `vector`

type.

For `weak_ordering`

, the values are `weak_ordering::less`

, `weak_ordering::equal`

and `weak_ordering::greater`

. The `weak_ordering`

implies equivalence class. For example in case-insensitive string comparison, `"str"`

and `"Str"`

may be `weak_ordering::equivalent`

but not actually equal.

For `partial_ordering`

, the values are `partial_ordering::less`

, `partial_ordering::equal`

, `partial_ordering::greater`

and an additional `partial_ordering::unordered`

.

# Primary and secondary operators

The `operator<=>()`

is now considered to be a primary operator to define the secondary operators. This taxonomy is a result of the idiom that authors used `operator<()`

to define other operators, but this had problems as described in the previous section. So the `operator<()`

is not a primary operator anymore. The primary and secondary operators are given in the table below.

This taxonomy allows us to define two properties: reversibility and rewritability.

## Primary operators are reversible

Expressions like `a==2`

and `2==a`

must be the same. But the latter gives an error in C++17. For this to be legible we need to write `operator==(int, A)`

and make it a non-member `friend`

.

Equality should be symmetric. i.e., `a==2`

and `2==a`

must be the same. In C++20 comparison of `2==a`

evaluates to `a.operator==(2)`

, thus the language understands that equality must be symmetric.

Following this, for the `operator<=>()`

, `a <=> 4`

works and evaluates as `a.operator<=>(4)`

while `4 <=> a`

would have been considered to be ill-formed in C++17 (if the operator had existed then). In C++20, operator<=> is symmetric as well. Overload resolution for `4 <=> a`

will find the member function `operator<=>(A, int)`

and consider a synthetic candidate `operator<=>(int, A)`

. This reversed candidate will be selected by the overload resolution.

However, `4 <=> a`

does not evaluate as `a.operator<=>(4)`

. Instead, it evaluates as `0 <=> a.operator<=>(4)`

. Its important to note that no actual new functions are generated by the compiler. The expressions are simply rewritten in terms of the reversed candidates.

## Secondary operators are rewritable

If we define only operator `==`

and not `!=`

then an expression like `a != 4`

will be considered ill-formed in C++17. But in C++20, expressions containing secondary comparison operators will also try to look up their corresponding primary operators and write the secondary comparison in terms of the primary comparison operator.

The expression `a != 4`

can be written as `!(a == 4)`

. The language understands that and thus so this expression will look up the `operator!=()`

and also `operator==()`

. Here `a!=4`

will thus evaluate as `!a.operator==(4)`

Similarly the ordering secondary operators are also rewritten. `a @ b`

gets evaluated as `(a <=> b) @ 0`

. For example: `b < 4`

is evaluated as `b.operator<=>(4) < 0`

.

In these cases too the no new operators are generated but only evaluated differently.

# Using the default

To write all comparisons for user-defined type, we just need to write the `operator<=>()`

that returns the appropriate category type. This is less error-prone and allows us to write easily understandable code.

To be specific we only need to write the primary operators, for example:

```
struct A{
T t; U u; //...
bool operator==( A const& rhs) const {
// ...
}
strong_ordering operator<=>( A const& rhs) const {
// ...
}
};
```

which gives us a lot less code than writing all the operators.

In C++20 since weโre just doing the default member-wise lexicographical comparison. we can use the default and let the compiler generate them.

```
struct A {
T t; U u; //...
bool operator==(A const& rhs) const = default;
strong_ordering operator<=>(A const& rhs) const = default;
};
```

or further simplified to get both defaulted `operator==()`

and defaulted `operator<=>()`

.

```
struct A {
T t; U u; //...
auto operator<=>(A const& rhs) const = default;
};
```

# Example

```
#include <compare>
#include <iostream>
using namespace std;
class Point {
int x;
int y;
public:
Point(int x, int y):x(x), y(y){}
auto operator<=>(const Point&) const = default;
};
int main() {
Point p1(2, 2), p2(4, 3);
cout << boolalpha << (p1 == p2) << endl; // false
cout << (p1 != p2) << endl; // true
cout << (p1 < p2) << endl; // true
cout << (p1 <= p2) << endl; // true
cout << (p1 > p2) << endl; // false
cout << (p1 >= p2) << endl; // false
return 0;
}
```

In this example, the default comparison operators are also generated by the compiler.

Note that the equality operator is also generated. Equality is defined to be the equality of members and base classes. In this case of `Point`

s, the corresponding values of `x`

and `y`

are compared.

# Conclusion

The new addition of the spaceship operator has considerably changed how the comparison is viewed. It has affected how comaprison semantics work at the language level.