Home Item 29 - Three-way Comparison Operator <=> (English)
Post
Cancel

Item 29 - Three-way Comparison Operator <=> (English)

Three-way Comparison Operator

When writing your own struct or class, sometimes you need to compare objects, which involves implementing operators like <, <=, ==, !=, >=, and >. Implementing all six operators can be tedious and error-prone.

C++20 introduced the three-way comparison operator (spaceship operator, <=>), a new tool that unifies and simplifies comparison operations.

The main function of the three-way comparison operator <=> is to:

Unify Comparison Operators: By defining just one operator, all the comparison operators (<, <=, ==, !=, >=, >) can be automatically generated.

code

Below, we define a Point in three-dimensional space with x, y, and z coordinates. We will then define how to compare these points.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <compare>

class Point {
public:
    Point(int x, int y, int z) : x(x), y(y), z(z) {}

    // Use default to automatically generate the comparison operator
    auto operator<=>(const Point& other) const = default;

    // Friend function declaration
    friend std::ostream& operator<<(std::ostream& os, const Point& point);

private:
    int x, y, z;
};

// Friend function declaration
std::ostream& operator<<(std::ostream& os, const Point& point) {
    os << "Point(" << point.x << ", " << point.y << ", " << point.z << ")";
    return os;
}

auto printHelper(bool result)
{
    if (result)
        return "True";
    else
        return "False";
}

int main() {

    Point p1{ 2, 1, 3 };
    Point p2{ 1, 1, 4 };
    Point p3{ 3, 3, 4 };

    std::cout << p1 << std::endl;
    std::cout << p2 << std::endl;
    std::cout << p3 << std::endl;

    std::cout << "------------" << std::endl;

    // Compare p1 and p2
    std::cout << "(p1 == p2): " << printHelper(p1 == p2) << std::endl;
    std::cout << "(p1 < p2): " << printHelper(p1 < p2) << std::endl;
    std::cout << "(p1 > p2): " << printHelper(p1 > p2) << std::endl;

    // Compare p1 and p3
    std::cout << "(p1 == p3): " << printHelper(p1 == p3) << std::endl;
    std::cout << "(p1 < p3): " << printHelper(p1 < p3) << std::endl;
    std::cout << "(p1 > p3): " << printHelper(p1 > p3) << std::endl;

    return 0;
}

Execution result:

Comparison order x -> y -> z

Desktop View

When you use auto operator<=>(const Point& other) const = default, the compiler automatically generates the three-way comparison operator and compares members in the order they are declared. This is equivalent to the manual comparison logic.

The comparison logic generated by the compiler for the three-way comparison operator is as follows:

  1. Member Variable Order: The compiler compares member variables in the order they are declared. This applies regardless of whether the members are public, protected, or private. The compiler considers all member variables to generate the comparison logic.
  2. Using <=>: Each member variable is compared using the <=> operator.
  3. Non-equal Result: If the comparison result of any member variable is not equal, that result is returned.
  4. All Members Equal: If all member variables are equal, the result is equal.

Manually Defining the Three-way Comparison Operator

If you want to define the comparison order yourself, e.g., comparing z -> y -> x, you can do it manually:

1
2
3
4
5
6
7
8
9
10
11
12
 auto operator<=>(const Point& other) const {
     if (auto cmp = z <=> other.z; cmp != 0) {
         return cmp;
     }

     if (auto cmp = y <=> other.y; cmp != 0) {
         return cmp;
     }

     return x <=> other.x;
 }

However, this will result in a compile-time error.

Desktop View

The error occurs because when you define the three-way comparison operator <=>, the compiler expects you to also provide a definition for the equality operator ==. While <=> can generate <, <=, >, and >=, it does not automatically generate == and !=.

You need to manually define the equality operator:

1
2
3
4
 bool operator==(const Point& other) const {
     return x == other.x && y == other.y && z == other.z;
 }

Desktop View

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <compare>

class Point {
public:
    Point(int x, int y, int z) : x(x), y(y), z(z) {}

    auto operator<=>(const Point& other) const {
        if (auto cmp = z <=> other.z; cmp != 0) {
            return cmp;
        }

        if (auto cmp = y <=> other.y; cmp != 0) {
            return cmp;
        }

        return x <=> other.x;
    }


    bool operator==(const Point& other) const {
        return x == other.x && y == other.y && z == other.z;
    }


    friend std::ostream& operator<<(std::ostream& os, const Point& point);

private:
    int x, y, z;
};

std::ostream& operator<<(std::ostream& os, const Point& point) {
    os << "Point(" << point.x << ", " << point.y << ", " << point.z << ")";
    return os;
}

auto printHelper(bool result)
{
    if (result)
        return "True";
    else
        return "False";
}

int main() {

    Point p1{ 2, 1, 3 };
    Point p2{ 1, 1, 4 };
    Point p3{ 3, 3, 4 };

    std::cout << p1 << std::endl;
    std::cout << p2 << std::endl;
    std::cout << p3 << std::endl;

    std::cout << "------------" << std::endl;

    std::cout << "(p1 == p2): " << printHelper(p1 == p2) << std::endl;
    std::cout << "(p1 < p2): " << printHelper(p1 < p2) << std::endl;
    std::cout << "(p1 > p2): " << printHelper(p1 > p2) << std::endl;

    std::cout << "(p1 == p3): " << printHelper(p1 == p3) << std::endl;
    std::cout << "(p1 < p3): " << printHelper(p1 < p3) << std::endl;
    std::cout << "(p1 > p3): " << printHelper(p1 > p3) << std::endl;

    return 0;
}

Use Cases

  1. When You Need to Compare Objects: For sorting or comparing objects, such as in sorting algorithms, binary search trees, priority queues, etc.
  2. When Multiple Comparison Operators are Needed: When a class needs multiple comparison operators (<, <=, >, >=, ==, !=), using <=> simplifies the definition of these operators.

  3. Implementing Custom Sorting Logic: When a class needs custom sorting logic, the <=> operator can be customized to achieve this.

That’s all.

☝ツ☝

This post is licensed under CC BY 4.0 by the author.

👈 ツ 👍