I have a project targeting C++17, which defines some structs without comparison operators (because they are not required and noone could be bothered to define them). I am then unit testing that code in C++20 and could use default comparisons provided by defaulted spaceship operator:
// foo.hpp
struct foo {
int x{};
};
// foo_test.cpp
#include "foo.hpp"
#include <compare>
auto operator<=>(const foo&, const foo&) = default;
I am getting an error:
Three-way comparison operator is not a friend of 'foo'
While I understand this error to just mean "you cannot declare this operator out of class", a mention of a friend is a bit confusing to me, as it is a POD with all fields public, so friend function does not have any more access than a non-friend one. Same thing happens for operator==
.
Is there any way to achieve default comparison without modifying the original header? I know I could define it inline wrapped in #if __cplusplus >= 202002L
, but would rather avoid it.
Edit:
Is it possible to define a defaulted comparison operator for a C struct? says it is not possible directly, but I would also accept some workarounds like magic_comparator(a) == magic_comparator(b);
, maybe something using anonymous unions or other hacks.
std::tuple
implements the operator<=>
for a collection of objects similar to the synthesized operator<=>
. You just need a way to get a tuple of the members.
In C++26, this is easily done with structured binding packs:
template<typename T>
constexpr auto tie_of(const T& x) noexcept {
auto&& [ ...members ] = x;
return std::tie(members...);
}
This function is also given by Boost.PFR (AKA 'magic get') as boost::pfr::structure_tie
, which you can even use in C++20 (or in C++17 if you use operator<
/operator<=
/etc. instead)
You can also just manually define it:
constexpr auto tie_of(const foo& obj) noexcept {
const auto& [ x ] = obj;
return std::tie(x);
}
But this has the downside of needing to manually keep it up-to-date. It will at least fail to compile if the number of members changes because of the structured binding, so it will never silently ignore members.
Then the comparison functions can be defined easily as https://godbolt.org/z/Kz9GY1rnz:
constexpr bool operator==(const foo& l, const foo& r) noexcept(noexcept(tie_of(l) == tie_of(r))) {
return tie_of(l) == tie_of(r);
}
constexpr auto operator<=>(const foo& l, const foo& r) noexcept(noexcept(tie_of(l) <=> tie_of(r))) {
return tie_of(l) <=> tie_of(r);
}
This has the downside that noexcept
/constexpr
/operator==
are no longer 'automatic'. It can easily be macro-ised though.
You can also just use tie_of
as your magic_comparator
.