EDIT: This has nothing to do with spaceship. It is just that the use of spaceship obfuscated the real issue in my code (see answer for details).
I was surprised by the output of this program: (If you like puzzles feel free to open the Godbolt link and try to spot the cause yourself)
#include <cstdint>
#include <cassert>
#include <compare>
#include <cmath>
#include <iostream>
#include <limits>
template<typename T>
struct TotallyOrdered
{
T val;
constexpr TotallyOrdered(T val) :
val(val) {}
constexpr operator T() const { return val; }
constexpr std::strong_ordering operator<=>(TotallyOrdered const& other) const
{
if (std::isnan(val) && std::isnan(other.val))
{
return std::strong_ordering::equal;
}
if (std::isnan(val))
{
return std::strong_ordering::less;
}
if (std::isnan(other.val))
{
return std::strong_ordering::greater;
}
if (val < other.val)
{
return std::strong_ordering::less;
}
else if (val == other.val)
{
return std::strong_ordering::equal;
}
else
{
assert(val > other.val);
return std::strong_ordering::greater;
}
}
};
int main()
{
const auto qNan = std::numeric_limits<float>::quiet_NaN();
std::cout << std::boolalpha;
std::cout << ((TotallyOrdered{qNan} <=> TotallyOrdered{1234.567}) == std::strong_ordering::less) << std::endl;
std::cout << ((TotallyOrdered{qNan} <=> TotallyOrdered{1234.567}) == std::strong_ordering::equal) << std::endl;
std::cout << ((TotallyOrdered{qNan} <=> TotallyOrdered{1234.567}) == std::strong_ordering::equivalent) << std::endl;
std::cout << ((TotallyOrdered{qNan} <=> TotallyOrdered{1234.567}) == std::strong_ordering::greater) << std::endl;
}
output:
false
false
false
false
After a bit of blaming Godbolt caching... I have figured out that the problem is that I was comparing TotallyOrdered<float>
and TotallyOrdered<double>
(adding f
after 1234.567
gives expected output).
My questions are:
strong_order
<=>
.std::strong_ordering
result) compile, preventing comparisons that give std::partial_ordering
?It's allowed because your conversion operator to T
is not explicit. This allows both sides of the comparison to undergo a user-defined conversion to their respective T
. So you end up with a float
and a double
. And then those can both be converted to double
and a comparison can happen. But that comparison returns an std::partial_ordering
, not an std::strong_ordering
.
Note that std::strong_ordering
can be compared to a bool, which is why your code compiles in the first place. Although cppreference.com does note that:
The behavior of a program that attempts to compare a strong_ordering with anything other than the integer literal 0 is undefined.
I'm not a 100% sure whether your program is displaying undefined behavior, or whether there's some more conversion/promotion "magic" going on.
Either way, if you change your conversion operator to be explicit, the code won't compile anymore. Which I guess is what you actually want?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With