Some time ago I defined my first three-way comparison operator. It compared a single type and replaced multiple conventional operators. Great feature. Then I tried to implement a similar operator for comparing two variants by delegation:
auto operator <=> (const QVariant& l, const QVariant& r)
{
switch (l.type())
{
case QMetaType::Int:
return l.toInt() <=> r.toInt();
case QMetaType::Double:
return l.toDouble() <=> r.toDouble();
default:
throw;
}
}
This doesn't compile, I get the error
inconsistent deduction for auto return type: ‘std::strong_ordering’ and then ‘std::partial_ordering’.
Obviously int
and double
spaceship operators return different types.
What is the correct way to solve this?
Same way you resolve any other function which returns auto
in which different return
statements deduce differently. You either:
return
s have the same type, orIn this case, int
s compare as strong_ordering
while double
s compare as partial_ordering
, and strong_ordering
is implicitly convertible to partial_ordering
, you can do either:
std::partial_ordering operator <=>(const QVariant& l, const QVariant& r) {
// rest as before
}
or explicitly cast the integer comparison:
case QMetaType::Int:
return std::partial_ordering(l.toInt() <=> r.toInt());
That gives you a function returning partial_ordering
.
If you want to return strong_ordering
instead, you have to lift the double
comparison to a higher category. You can do that in two ways:
You can use std::strong_order
, which is a more expensive operation, but provides a total ordering over all floating point values. You would then write:
case QMetaType::Double:
return std::strong_order(l.toDouble(), r.toDouble());
Or you can do something like consider NaN
s ill-formed and throw them out somehow:
case QMetaType::Double: {
auto c = l.toDouble() <=> r.toDouble();
if (c == std::partial_ordering::unordered) {
throw something;
} else if (c == std::partial_ordering::less) {
return std::strong_ordering::less;
} else if (c == std::partial_ordering::equivalent) {
return std::strong_ordering::equal;
} else {
return std::strong_ordering::greater;
}
}
It's more tedious but I'm not sure if there's a more direct way to do this kind of lifting.
The types of the operator<=>
for int
and double
differ but they should have a common type. You probably want to leverage the compiler in automatically finding the proper type. You could use std::common_type
to do but that would be quite ugly. It is easier to just leverage what std::common_type
type does under the (when implemented in the library rather the compiler) and use the ternary operator:
auto operator <=> (const QVariant& l, const QVariant& r)
{
return l.type() == QMetaType:Int? l.toInt() <=> r.toInt()
: l.type() == QMetaType::Double? l.toDouble() <=> r.toDouble()
: throw;
}
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