【问题标题】:Three-way comparison operator with inconsistent ordering deduction顺序推导不一致的三向比较算子
【发布时间】:2021-03-29 11:10:54
【问题描述】:

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

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 spaceship 运算符返回不同的类型。

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

【问题讨论】:

  • &lt;=&gt; 不需要表现得对称吗?仅打开 l.type() 就违反了该属性。
  • @Bergi 你是对的。这就是为什么在我的真实代码中我要检查类型的相等性。

标签: c++ qt c++20 return-type-deduction spaceship-operator


【解决方案1】:

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

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

在这种情况下,ints 与strong_ordering 比较,而doubles 与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 格式不正确并以某种方式将它们扔掉:

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

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

【讨论】:

  • 在部分排序可以简单地转换为相应的强排序的情况下,std::strong_order 是否比您上次的建议有性能成本?也就是说,这三个案例与std::strong_order 对这些案例所做的事情相同吗? (如果您认为此类值不正确并且希望快速失败,可能仍有理由抛出无序结果,因此无论哪种方式,这都是一个很好的建议,但我试图更好地理解这些选项。)
  • @KRyan 是的。 std::strong_order 实际上确实提供了浮点的总顺序,包括所有 NaNs(这是 ISO/IEC/IEEE 60559 totalOrder)。这肯定比分支更多的工作——但它也是非常不同的行为。
  • 我明白这一点,但我特别询问了那些相同的分支。看起来那些分支应该是一样的,如果值使用这些分支,性能应该是一样的,不是吗?
  • @KRyan 我不明白你在问什么。
【解决方案2】:

operator&lt;=&gt;intdouble 的类型不同,但它们应该有一个共同的类型。您可能希望利用编译器自动查找正确的类型。您可以使用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;
}

【讨论】:

  • 听起来是两种类型的绝妙解决方案。但是好吧,我的现实生活中的功能将有几十种。那会一团糟,对吧?
  • @Silicomancer:怎么会这样?您需要列出您的类型以及如何提取它们,我已经为您设置了如何轻松阅读多个案例。如果您愿意,可以将逻辑放入一个可变参数函数中,在该函数中您可以有效地将类型拼写为 accessor-lmbda 函数并在那里拥有逻辑,但它只会分解为相同的东西跨度>
  • 皇家倾向,是的 ;-) 嗯,我也想到了使用可变参数函数。我想这可能是一个更好的解决方案。
  • @Silicomancer 实际上取决于你如何旋转它。只是对于这个用例,它会更多的打字和混淆。如果您有一个合适的二进制apply() 函数,它以操作(在本例中为[](auto const&amp; l, auto const&amp; r)( return l &lt;=&gt; r; })作为参数,它可能有点好:return apply(op, l, r); 然而,写apply() 有点烦人,尽管是机械的。跨度>
  • 看看我的回答,你怎么看?
【解决方案3】:

我使用了一些模板代码来实现 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 调用中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-07
    • 2020-05-06
    • 2016-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-11
    相关资源
    最近更新 更多