18

前段时间我定义了我的第一个三路比较运算符。它比较了单一类型并替换了多个常规运算符。很棒的功能。然后我尝试实现一个类似的运算符来通过委托比较两个变体:

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;
   }
}

这不编译,我得到错误

自动返回类型的扣除不一致:'std::strong_ordering' 然后是 'std::partial_ordering'。

显然intdouble飞船操作员返回的类型不同。

解决这个问题的正确方法是什么?

4

3 回答 3

21

以同样的方式解决任何其他返回auto的函数,其中不同的return语句推导不同。你要么:

  1. 确保所有returns 具有相同的类型,或者
  2. 明确选择返回类型。

在这种情况下,ints compare asstrong_orderingdoubles compare as partial_ordering,并且strong_ordering可以隐式转换为partial_ordering,您可以执行以下任一操作:

std::partial_ordering operator <=>(const QVariant& l, const QVariant& r) {
    // rest as before
}

或显式转换整数比较:

      case QMetaType::Int:
         return std::partial_ordering(l.toInt() <=> r.toInt());

这给了你一个返回的函数partial_ordering


如果您想返回strong_ordering,则必须将double比较提升到更高的类别。您可以通过两种方式做到这一点:

您可以使用std::strong_order,这是一个更昂贵的操作,但提供了对所有浮点值的总排序。然后你会写:

      case QMetaType::Double:
         return std::strong_order(l.toDouble(), r.toDouble());

或者你可以做一些像认为NaNs ill-formed 的事情并以某种方式将它们扔掉:

      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;
         }
      }

这更乏味,但我不确定是否有更直接的方法来进行这种提升。

于 2020-12-18T20:00:04.403 回答
4

operator<=>forint和的类型double不同,但它们应该有一个共同的类型。您可能希望利用编译器自动查找正确的类型。你可以std::common_type做,但那会很丑陋。更容易利用std::common_type在(在库而不是编译器中实现时)下的类型并使用三元运算符:

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;
}
于 2020-12-18T20:07:09.570 回答
0

我玩弄了一些模板代码来实现 Dietmar Kühls 使用std::common_type. 这是结果示例代码:

template <typename CommonT, typename... ArgsT> requires (sizeof...(ArgsT) == 0)
inline CommonT variantSpaceshipHelper([[maybe_unused]] const QVariant& pLeft, [[maybe_unused]] const QVariant& pRight) noexcept
{
   std::terminate(); // Variant type does not match any of the given template types
}

template <typename CommonT, typename T, typename... ArgsT>
inline CommonT variantSpaceshipHelper(const QVariant& pLeft, const QVariant& pRight) noexcept
{
   if (pLeft.type() == static_cast<QVariant::Type>(qMetaTypeId<T>()))
   {
      return (pLeft.value<T>() <=> pRight.value<T>());
   }

   return variantSpaceshipHelper<CommonT, ArgsT...>(pLeft, pRight);
}

template <typename... ArgsT>
inline auto variantSpaceship(const QVariant& pLeft, const QVariant& pRight) noexcept
{
   using CommonT = std::common_type_t<decltype(std::declval<ArgsT>() <=> std::declval<ArgsT>())...>;
   return variantSpaceshipHelper<CommonT, ArgsT...>(pLeft, pRight);
}

inline auto operator <=>(const QVariant& pLeft, const QVariant& pRight) noexcept
{
   assert(pLeft.type() == pRight.type());
   return variantSpaceship<int, double>(pLeft, pRight);
}

可以轻松地将其他类型添加到variantSpaceship调用中。

于 2020-12-19T23:25:28.027 回答