Three-way comparison operator with inconsistent ordering deduction

Same way you resolve any other function which returns auto in which different return statements deduce differently. You either:

  1. Ensure that all the returns have the same type, or
  2. Explicitly pick a return type.

In this case, ints compare as strong_ordering while doubles 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 NaNs 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;