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
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:
- 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.
- Using <=>: Each member variable is compared using the <=> operator.
- Non-equal Result: If the comparison result of any member variable is not equal, that result is returned.
- 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.
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;
}
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
- When You Need to Compare Objects: For sorting or comparing objects, such as in sorting algorithms, binary search trees, priority queues, etc.
When Multiple Comparison Operators are Needed: When a class needs multiple comparison operators (<, <=, >, >=, ==, !=), using
<=>
simplifies the definition of these operators.- Implementing Custom Sorting Logic: When a class needs custom sorting logic, the <=> operator can be customized to achieve this.
That’s all.