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:
- Why is this allowed? (Not asking if this is standard behavior; it is, but curious about design intention.)
- Why do comparisons give none of the "enums" in strong_ordering? It looks like I get a partial order when I do a mixed comparison although I have defined only
strong_order<=>. - How can I force that only "exact +-cvref" comparisons (that give a
std::strong_orderingresult) compile, preventing comparisons that givestd::partial_ordering?