Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Modern C++20/23

Three-Way Comparison (C++20)

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;
Conceptual Check

What does the spaceship operator return?

Runtime Environment

Interactive Lab

1#include <iostream>
2#include <compare>
3 
4struct Point {
5 int x, y;
6
7 auto operator<=>(const Point&) const = default;
8 bool operator==(const Point&) const = default;
9};
10 
11int main() {
12 Point p1{1, 2}, p2{3, 4}, p3{1, 2};
13
14 std::cout << std::boolalpha;
15 std::cout << "p1 < p2: " << (p1 < p2) << '\n';
16 std::cout << "p1 == p3: " << (p1 == p3) << '\n';
17 std::cout << "p2 > p1: " << (p2 > p1) << '\n';
18 std::cout << "p1 != p2: " << (p1 != p2) << '\n';
19
20 return 0;
21}
System Console

Waiting for signal...