Why is this an issue?
C++20 introduces the "spaceship" operator<=>
that replaces all the other comparison operators in most cases. When this operator
is defined, the compiler can rewrite expressions using <
, <=
, >
and >=
to use this
operator instead. This presents three advantages:
- Less code to write (and therefore fewer bugs, too),
- Guaranteed consistency between all the comparison operators (for instance, in this situation,
a < b
and !(a >=
b)
will always return the same value).
- Guaranteed symmetry for comparisons: if you can write
a < b
, and that operation is resolved through
operator<=>
, you can also write b < a
, and get a consistent result. Achieving the same result with classical
comparison operators requires twice as many overloads if a
and b
have different types.
Additionally, if the operator<=>
has the defaulted implementation, the compiler can implicitly generate a defaulted
implementation of operator==
, simplifying the class definition one step further.
Before C++20, it was common to provide only operator<
for a class and ask the users of this class to write all their code only
using this operator (this is what std::map
requires of its key type, for instance). In this case, it is still advised to replace the
operator with <=>
: the quantity of required work is similar, and users of the class will benefit from a much greater
expressivity.
How to fix it
This rule reports user-provided comparison operators (member functions or free functions) <
, <=
, >
and >=
that could be replaced by defining the operator<=>
.
Code examples
Noncompliant code example
class A { // Noncompliant: defines operator< that can be replaced with operator<=>
int field;
public:
bool operator<(const A& other) const {
return field < other.field;
}
};
Compliant solution
class A {
int field;
public:
auto operator<=>(const A& other) const = default;
// Note that here, operator == will be implicitly defaulted
};
Noncompliant code example
class B;
class C { // Noncompliant: defines 12 comparison operators that can be replaced with 2 operators
int field;
public:
bool operator==(const B&) const;
bool operator!=(const B&) const;
bool operator<=(const B&) const;
bool operator<(const B&) const;
bool operator>=(const B&) const;
bool operator>(const B&) const;
friend bool operator==(const B&, const C&);
friend bool operator!=(const B&, const C&);
friend bool operator<=(const B&, const C&);
friend bool operator<(const B&, const C&);
friend bool operator>=(const B&, const C&);
friend bool operator>(const B&, const C&);
};
Compliant solution
class B;
class C { // Compliant. the same comparisons are possible as with the 12 operators
int field;
public:
auto operator<=>(const B&) const;
auto operator==(const B&) const;
};
Noncompliant code example
enum class MyEnum {low = 1, high = 2};
bool operator<(MyEnum lhs, MyEnum rhs) { // Noncompliant: can be replaced with operator<=>
return static_cast<int>(lhs) < static_cast<int>(rhs);
}
Compliant solution
enum class MyEnum {low = 1, high = 2};
auto operator<=>(MyEnum lhs, MyEnum rhs) {
return static_cast<int>(lhs) <=> static_cast<int>(rhs);
}
Resources
Documentation
Related rules
- S6186 - Redundant comparison operators should not be defined.
- S6230 - Comparision operators (
<=>
, ==
) should be defaulted unless non-default behavior is required