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 require to double the number overloads if a
and b
are of different types.
Additionally, if the operator<=>
has the defaulted implementation, the compiler can also implicitly generate a defaulted
implementation of operator==, simplifying the class definition one step further.
Before C++20, if was common to provide only operator<
for a class, and ask the user of this class to write all his code only using
this operator (this is what std::map
requires of its key type, for instance). It is still advised in this case to replace the operator
with <=>
: The quantity of required work is similar, and the user of the class will benefit from a much greater expressivity.
This rule reports user-provided comparison operators (member functions or free functions) <
, <=
, >
and >=
.
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;
}
};
class C;
class B { // Noncompliant: defines 12 comparison operators that can be replaced with 2 operators
int field;
public:
bool operator==(const C&) const;
bool operator!=(const C&) const;
bool operator<=(const C&) const;
bool operator<(const C&) const;
bool operator>=(const C&) const;
bool operator>(const C&) const;
friend bool operator==(const C&, const B&);
friend bool operator!=(const C&, const B&);
friend bool operator<=(const C&, const B&);
friend bool operator<(const C&, const B&);
friend bool operator>=(const C&, const B&);
friend bool operator>(const C&, const B&);
};
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
class A {
int field;
public:
auto operator<=>(const A& other) const = default;
// Note that here, operator == will be implicitly defaulted
};
class B { // Compliant. the same comparisons are possible as with the 12 operators
int field;
public:
auto operator<=>(const C&) const;
auto operator==(const C&) const;
};
enum class MyEnum {low = 1, high = 2};
auto operator<=>(MyEnum lhs, MyEnum rhs) {
return static_cast<int>(lhs) <=> static_cast<int>(rhs);
}
Resources
- {rule:cpp:S6186} - Redundant comparison operators should not be defined.