The Spaceship Operator and Expression Rewriting

A guide to simplifying our comparison operators using C++20 features

Ryan McCombe
Updated

Let's imagine we have the following custom type, which simply stores a value, and implements an == operator:

class Number {
public:
  bool operator==(const Number& Other) const {
    std::cout << "Hello from the == operator\n";
    return Value == Other.Value;
  }

  int Value;
};

We create two objects of this type, and compare them using the != operator:

int main(){
  Number A{1};
  Number B{2};

  if (A != B) { std::cout << "Not equal!"; }
}

Our type doesn't have the != operator, so we'd expect this to fail. In C++17 and earlier, that's exactly what happens:

error: binary '!=': 'Number' does not define this operator

But, from C++20 onwards, this program will compile, and run as we expect:

Hello from the == operator
Not equal!

This works because, behind the scenes, the compiler has rewritten our expression.

Expression Rewriting

As the previous output would indicate, the expression using the != operator is calling the == operator of our class.

This is an example of expression rewriting, which was added to comparison operators in C++20

Specifically, if our type doesn't have the != operator, the compiler can rewrite any expression using it in terms of the == operator. So, A != B will be rewritten as !(A == B)

Specifically, any expression using a secondary comparison operator can be rewritten in terms of the corresponding primary comparison operator.

  • The secondary comparison operator != corresponds to the primary comparison operator ==
  • The secondary comparison operators !=, <, <=, > and >= correspond to the primary comparison operator <=>, which we'll cover next

The <=> Operator

To support expression rewriting, the three-way comparison operator was added in C++20 and uses the syntax <=>:

A <=> B

Because of its visual appearance, it is often called the Spaceship Operator

The <=> operator accepts two operands, and returns one of three possible values. The return value depends on whether the left operand is less than, equal to, or greater than the right operand.

These three possibilities are contained within the std::strong_ordering struct:

  • If A < B it returns std::strong_ordering::less
  • If A == B it returns std::strong_ordering::equal
  • If A > B it returns std::strong_ordering::greater

In code, we could use the <=> operator and std::strong_ordering type like this:

if (A <=> B == std::strong_ordering::less) {
  std::cout << "A is less than B";
}

if (A <=> B == std::strong_ordering::equal) {
  std::cout << "A is equal to B";
}

if (A <=> B == std::strong_ordering::greater) {
  std::cout << "A is greater than B";
}

The previous example shows the mechanics of the <=> operator and the std::strong_ordering type, but it's quite unusual that we'd use them like this. The <=> syntax and std::strong_ordering reference will typically only be used in one place - in our class code, to define the three-way comparison operation.

Any other code seeking to compare our objects will still use the regular comparison operators, like > and >=, to return booleans.

The key point is that, as of C++20, these operators no longer need to be defined. The compiler can rewrite any expression that attempts to use them to secretly call the <=> operator instead

Below, our class only defines the <=> operator, but because of expression rewriting, code using our class can use regular comparison operators, like < and >=:

#include <iostream>

class Number {
public:
  // Our class defines the <=> operator
  // which returns a strong_ordering...
  std::strong_ordering operator<=>(
    const Number& Other) const{
    std::cout << "Hello from <=>\n";
    return Value <=> Other.Value;
  }

  int Value;
};

int main(){
  Number A{1};
  Number B{2};

  // ...but our consumers use the normal
  // comparison operators to return booleans
  if (A < B) { std::cout << "A < B\n"; }
  if (A <= B) { std::cout << "A <= B\n"; }
}
Hello from <=>
A < B
Hello from <=>
A <= B

In summary, starting with C++20, we now only need to define the two primary comparison operators for our types: == and <=>. All of the secondary comparison operators can be automatically generated from these.

Summary

In this lesson, we explored the power of C++20's spaceship operator (<=>) and expression rewriting, showcasing how these features make it easier to implement comparison operators for custom types.

Key Takeaways

  • Through the use of these C++20 features, only two primary comparison operators need to be defined (== and <=>).
  • The spaceship operator (<=>) enables three-way comparisons, returning one of three possible outcomes: less, equal, or greater.
  • Expression rewriting allows the compiler to automatically generate secondary comparison operators (!=, <, <=, >, >=) from the primary comparison operators (==, <=>).
  • std::strong_ordering is used with the spaceship operator to define the outcome of comparisons.
  • An explicit definition of == is necessary alongside <=> to improve our type's runtime performance.
Next Lesson
Lesson 109 of 128

C++20 Modules

A detailed overview of C++20 modules - the modern alternative to #include directives. We cover import and export statements, partitions, submodules, how to integrate modules with legacy code, and more.

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Defining the == Operator
Why do we need to explicitly define the == operator if we already have <=>?
Equality vs. Equivalence
What are the differences between std::strong_ordering::equal and std::strong_ordering::equivalent?
Benefits of the Spaceship Operator
What are the benefits of using the spaceship operator <=> over traditional comparison operators?
Custom Comparison Logic
How can we implement custom comparison logic using the spaceship operator <=>?
Using <=> for Sorting
Can <=> be used for sorting algorithms in C++20?
Niche Cases for Three-Way Comparison
How does the C++20 standard handle types that do not naturally fit into the three-way comparison model?
Real-World Examples of <=>
What are some practical examples of using <=> in real-world applications?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant