The Spaceship Operator (C++20)
C++20 introduces the three-way comparison operator <=> (nicknamed “spaceship”), dramatically simplifying comparison operator implementation.
Basic Syntax
#include <compare>
class Point {
int x_, y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
// Default three-way comparison
auto operator<=>(const Point&) const = default;
};
Point p1{1, 2}, p2{3, 4};
p1 < p2; // Automatically works
p1 <= p2; // Automatically works
p1 > p2; // Automatically works
p1 >= p2; // Automatically works
p1 == p2; // Requires separate == or also defaulted
p1 != p2; // Generated from ==
Comparison Categories
The spaceship operator returns one of three ordering types:
1. strong_ordering
Total order with substitutability:
std::strong_ordering::less
std::strong_ordering::equal
std::strong_ordering::greater
std::strong_ordering::equivalent // Same as equal
Use when equal values are truly identical:
class Integer {
int value_;
public:
std::strong_ordering operator<=>(const Integer& other) const {
return value_ <=> other.value_;
}
};
2. weak_ordering
Total order without substitutability:
std::weak_ordering::less
std::weak_ordering::equivalent
std::weak_ordering::greater
Use when equal values may not be substitutable (e.g., case-insensitive strings):
class CaseInsensitiveString {
std::string value_;
public:
std::weak_ordering operator<=>(const CaseInsensitiveString& other) const {
// Compare ignoring case
// "Hello" and "hello" are equivalent but not equal
}
};
3. partial_ordering
May have incomparable values:
std::partial_ordering::less
std::partial_ordering::equivalent
std::partial_ordering::greater
std::partial_ordering::unordered // Not comparable
Use for types like floating-point (NaN is unordered):
double a = std::nan(""), b = 1.0;
auto cmp = a <=> b; // Returns partial_ordering::unordered
Defaulted Spaceship
The compiler can generate <=> for you:
struct Person {
std::string name;
int age;
auto operator<=>(const Person&) const = default;
// Compares members lexicographically: first name, then age
};
Member-wise Comparison Order
Comparison happens in declaration order:
struct Record {
int priority; // Compared first
std::string name; // Compared second if priority equal
double value; // Compared third if name equal
auto operator<=>(const Record&) const = default;
};
Custom Spaceship
Implement custom logic:
class Version {
int major_, minor_, patch_;
public:
std::strong_ordering operator<=>(const Version& other) const {
if (auto cmp = major_ <=> other.major_; cmp != 0)
return cmp;
if (auto cmp = minor_ <=> other.minor_; cmp != 0)
return cmp;
return patch_ <=> other.patch_;
}
};
Equality vs Spaceship
For efficiency, you might want to define == separately:
class String {
std::string data_;
public:
// Fast equality check
bool operator==(const String& other) const {
return data_ == other.data_;
}
// Three-way comparison for ordering
std::strong_ordering operator<=>(const String& other) const {
return data_ <=> other.data_;
}
};
Default Both
Common pattern: default both operators:
struct Point {
int x, y;
bool operator==(const Point&) const = default;
auto operator<=>(const Point&) const = default;
};
Heterogeneous Comparisons
Compare different types:
class Temperature {
double celsius_;
public:
explicit Temperature(double c) : celsius_(c) {}
std::partial_ordering operator<=>(double fahrenheit) const {
double c = (fahrenheit - 32) * 5/9;
return celsius_ <=> c;
}
};
Temperature t(100);
t < 212.0; // Compare Celsius with Fahrenheit
Compiler-Generated Operators
With defaulted <=>, you get all six comparison operators:
struct Data {
int value;
auto operator<=>(const Data&) const = default;
};
// Generated automatically:
// <, <=, >, >=
// Also need == for these:
bool operator==(const Data&) const = default;
// Then != is also generated
Pre-C++20 vs C++20
Before C++20
class Point {
int x_, y_;
public:
bool operator==(const Point& other) const {
return x_ == other.x_ && y_ == other.y_;
}
bool operator!=(const Point& other) const {
return !(*this == other);
}
bool operator<(const Point& other) const {
return (x_ < other.x_) || (x_ == other.x_ && y_ < other.y_);
}
bool operator<=(const Point& other) const {
return !(other < *this);
}
bool operator>(const Point& other) const {
return other < *this;
}
bool operator>=(const Point& other) const {
return !(*this < other);
}
};
With C++20
class Point {
int x_, y_;
public:
auto operator<=>(const Point&) const = default;
bool operator==(const Point&) const = default;
};
// Done! All six operators generated
Special Cases
Comparison with std::tie
Before C++20 idiom:
bool operator<(const Point& other) const {
return std::tie(x_, y_) < std::tie(other.x_, other.y_);
}
C++20 makes this unnecessary:
auto operator<=>(const Point&) const = default;
What does the spaceship operator return?
Interactive Lab
Waiting for signal...