The autogenerated Equality Operator
You can define the three-way comparison operator or request it from the compiler with =default
. But do you know that you can also define or request the equality operator in C++20?
Before I dive into the autogenerated equality operator, I’d like to refresh your knowledge about the three-way comparison operator.
The Three-Way Comparison Operator
You can define the three-way comparison operator or request it from the compiler with =default
. You get all six comparison operators in both cases: ==, !=, <, <=, >,
and >=
.
// threeWayComparison.cpp #include <compare> #include <iostream> 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 <typename T> constexpr bool isLessThan(const T& lhs, const T& rhs) { return lhs < rhs; } int main() { std::cout << std::boolalpha << std::endl; MyInt myInt1(2011); MyInt myInt2(2014); std::cout << "isLessThan(myInt1, myInt2): " << isLessThan(myInt1, myInt2) << std::endl; MyDouble myDouble1(2011); MyDouble myDouble2(2014); std::cout << "isLessThan(myDouble1, myDouble2): " << isLessThan(myDouble1, myDouble2) << std::endl; std::cout << std::endl; }
The user-defined (1) and the compiler-generated (2) three-way comparison operators work as expected.
But there are a few subtle differences in this case. The compiler-deduced return type for MyInt
(1) supports strong ordering, and the compiler-deduced return type of MyDouble
supports partial ordering. Floating pointer numbers only support partial ordering because floating-point values such as NaN (Not a Number) can not be ordered. For example, NaN == NaN
is false
.
The compiler-generated three-way comparison operator needs the header <compare>
, which is implicit constexpr
and noexcept
. Additionally, it performs a lexicographical comparison. Lexicographical comparison means that all base classes are compared left to right and all non-static members in their declaration order.
Let’s assume I add a std::unordered_set
to both classes MyInt
and MyDouble
.
struct MyInt { int value; std::unordered_set<int> mySet; 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<double> mySet; explicit MyDouble(double val): value{val}, mySet{val} { } bool operator<=>(const MyDouble&) const = default; };
Requesting or defining the three-way comparison fails because std::unordered_set
does not support ordering. std::unordered_set
only support equality comparison, and so does MyInt
and MyDouble
.
Equality Operator
When you define or request the equality operator from the compiler with =default
, you automatically
get the equality and inequality operators: ==
, and !=
.
// equalityOperator.cpp #include <iostream> #include <tuple> #include <unordered_set> struct MyInt { int value; std::unordered_set<int> mySet; 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<double> mySet; explicit MyDouble(double val): value{val}, mySet{val} { } bool operator==(const MyDouble&) const = default; }; template <typename T> constexpr bool areEqual(const T& lhs, const T& rhs) { return lhs == rhs; } template <typename T> constexpr bool areNotEqual(const T& lhs, const T& rhs) { return lhs != rhs; } int main() { std::cout << std::boolalpha << '\n'; MyInt myInt1(2011); MyInt myInt2(2014); std::cout << "areEqual(myInt1, myInt2): " << areEqual(myInt1, myInt2) << '\n'; std::cout << "areNotEqual(myInt1, myInt2): " << areNotEqual(myInt1, myInt2) << '\n'; std::cout << '\n'; MyDouble myDouble1(2011.0); MyDouble myDouble2(2014.0); std::cout << "areEqual(myDouble1, myDouble2): " << areEqual(myDouble1, myDouble2) << '\n'; std::cout << "areNotEqual(myDouble1, myDouble2): " << areNotEqual(myDouble1, myDouble2) << '\n'; std::cout << '\n'; }
Now, I can compare MyInt
and MyDouble
for equality and inequality.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
I applied a neat trick in the program equalityOperator.cpp
. Can you spot it?
In the following example, I implemented the equality operator of MyInt
by chaining the equality operators of value
and mySet
.
struct MyInt { int value; std::unordered_set<int> mySet; 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 pretty ugly and error-prone if you have a class with more members
On the contrary, I used std::tie
to implement the equality operator in the program equalityOperator.cpp
.
struct MyInt { int value; std::unordered_set<int> mySet; 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 created tuples are lexicographically compared.
What’s Next?
In my next post, 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.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,