C++ programming language: The automatically generated equality operator
Most C++ developers will be familiar with the fact that the three-way comparison operator can be defined or requested by the compiler using =default. What is probably less known is that the equality operator can also be defined or requested in C++20?
Advertisement
Before I discuss the automatically generated equality operator, I would like to brush up on the key facts about the three-way comparison operator.
Rainer Grimm has been working as a software architect, team and training manager for many years. He enjoys writing articles on the programming languages C++, Python and Haskell, but also enjoys speaking frequently at specialist conferences. On his blog Modern C++ he deals intensively with his passion C++.
The three-way comparison operator
The three-way comparison operator can be defined or requested from the compiler with =default. In both cases you get all six comparison operators: ==, !=, and >=.
// threeWayComparison.cpp
#include
#include
struct MyInt {
int value;
explicit MyInt(int val): value{val} { }
auto operator(const MyInt& rhs) const { // (1)
return value rhs.value;
}
};
struct MyDouble {
double value;
explicit constexpr MyDouble(double val): value{val} { }
auto operator(const MyDouble&) const = default; // (2)
};
template
constexpr bool isLessThan(const T& lhs, const T& rhs) {
return lhs The user-defined (1) and compiler-generated (2) three-way comparison operators work as expected.
However, there are a few notable differences between the two three-way comparison operators. The compiler-inferred return type for MyInt (1) supports strict ordering, but the compiler-inferred return type for MyDouble (2) only supports partial ordering. Floating point numbers can only support partial ordering because values like NaN (Not a Number) cannot be ordered. For example, NaN == NaN evaluates to false.
The three-way comparison operator produced by the compiler, which is implicitly constexpr and noexcept, requires the header
Let’s say I add a std::unordered_set to the two classes MyInt and MyDouble.
struct MyInt {
int value;
std::unordered_set
explicit MyInt(int val): value{val}, mySet{val} { }
bool operator(const MyInt& rhs) const {
if (auto first = value rhs.value; first != 0) return first;
else return mySet rhs.mySet;
}
};
struct MyDouble {
double value;
std::unordered_set
explicit MyDouble(double val): value{val}, mySet{val} { }
bool operator(const MyDouble&) const = default;
};
Requesting or defining three-way comparison fails because std::unordered_set does not support ordering. std::unordered_set only supports equality comparisons, and this also applies to MyInt and MyDouble.
equality operator
If the equality operator is defined or requested by the compiler with =default, you automatically get the equality and inequality operators: ==, and !=.
// equalityOperator.cpp
#include
#include
#include
struct MyInt {
int value;
std::unordered_set
explicit MyInt(int val): value{val}, mySet{val} { }
bool operator==(const MyInt& rhs) const {
return std::tie(value, mySet) == std::tie(rhs.value, rhs.mySet);
}
};
struct MyDouble {
double value;
std::unordered_set
explicit MyDouble(double val): value{val}, mySet{val} { }
bool operator==(const MyDouble&) const = default;
};
template
constexpr bool areEqual ( const T & lhs , const T & rhs ) { return lhs == rhs ; } template
constexpr bool areNotEqual(const T& lhs, const T& rhs) {
return lhs != rhs;
}
int main() {
std::cout Now I can compare MyInt and MyDouble for equality and inequality.
I used a trick in the equalityOperator.cpp program – who recognizes it?
In the example below, I implemented MyInt’s equality operator by concatenating the equality operators of value and mySet.
struct MyInt {
int value;
std::unordered_set
explicit MyInt(int val): value{val}, mySet{val} { }
bool operator==(const MyInt& rhs) const {
if (auto first = value == rhs.value; first != 0) return first;
else return mySet == rhs.mySet;
}
};
This is quite error-prone and looks ugly if you have a class with multiple members.
In contrast, I used std::tie to implement the equality operator in the equalityOperator.cpp program.
struct MyInt {
int value;
std::unordered_set
explicit MyInt(int val): value{val}, mySet{val} { }
bool operator==(const MyInt& rhs) const {
return std::tie(value, mySet) == std::tie(rhs.value, rhs.mySet);
}
};
std::tie creates a tuple of lvalue references to its arguments. Finally, the generated tuples are compared lexicographically.
What’s next?
In my next article I will continue my journey through C++20 and write about std::span. std::span represents an object that refers to a contiguous sequence of objects. (map)
To home page