C++ 具有不一致序演绎的三元比较算子

C++ 具有不一致序演绎的三元比较算子,c++,qt,c++20,return-type-deduction,spaceship-operator,C++,Qt,C++20,Return Type Deduction,Spaceship Operator,不久前,我定义了我的第一个三向比较运算符。它比较了单一类型,并取代了多个传统的运营商。很棒的功能。然后,我尝试实现一个类似的运算符,用于通过委派比较两个变体: auto operator <=> (const QVariant& l, const QVariant& r) { switch (l.type()) { case QMetaType::Int: return l.toInt() <=> r.toI

不久前,我定义了我的第一个三向比较运算符。它比较了单一类型,并取代了多个传统的运营商。很棒的功能。然后,我尝试实现一个类似的运算符,用于通过委派比较两个变体:

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;
   }
}
自动运算符(常量QVariant&l、常量QVariant&r)
{   
开关(l.type())
{
案例QMetaType::Int:
返回l.toInt()r.toInt();
案例QMetaType::Double:
返回l.toDouble()r.toDouble();
违约:
投掷;
}
}
这没有编译,我得到了错误

自动返回类型的推断不一致:“std::strong\u ordering”和“std::partial\u ordering”

显然
int
double
宇宙飞船操作符返回不同的类型


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

解决任何其他返回
auto
的函数的方法相同,在这些函数中,不同的
return
语句的推导方式不同。你可以:

  • 确保所有返回的
    类型相同,或
  • 显式地选择返回类型
  • 在这种情况下,
    int
    s比较为
    strong\u排序
    ,而
    double
    s比较为
    partial\u排序
    ,并且
    strong\u排序
    可隐式转换为
    partial\u排序
    ,您可以执行以下任一操作:

    std::偏序运算符(常数QVariant&l,常数QVariant&r){
    //照旧休息
    }
    
    或显式强制转换整数比较:

    case QMetaType::Int:
    return std::partial_ordering(l.toInt()r.toInt());
    
    这将为您提供一个返回部分排序的函数


    如果您想返回
    strong\u排序
    ,则必须将
    double
    比较提升到更高的类别。您可以通过两种方式实现这一点:

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

    case QMetaType::Double:
    返回std::强顺序(l.toDouble(),r.toDouble());
    

    或者你可以做一些事情,比如考虑<代码>楠<代码>不正确的形式,然后把它们扔掉:

    case QMetaType::Double:{
    自动c=l.toDouble()r.toDouble();
    if(c==std::偏序::无序){
    扔东西;
    }else if(c==std::partial_ordering::less){
    返回std::strong_排序::less;
    }else if(c==std::偏序::等价){
    返回标准::强顺序::相等;
    }否则{
    返回std::strong\u排序::更大;
    }
    }
    

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

    对于
    int
    double
    操作符的类型不同,但它们应该有一个共同的类型。您可能希望利用编译器自动查找正确的类型。您可以使用
    std::common_type
    来执行此操作,但这将非常难看。只利用
    std::common_type
    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;
    }
    
    自动运算符(常量QVariant&l、常量QVariant&r)
    {   
    返回l.type()==QMetaType:Int?l.toInt()r.toInt())
    :l.type()==QMetaType::Double?l.toDouble()r.toDouble()
    :投掷;
    }
    
    我使用了一些模板代码来实现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);
    }
    
    模板需要(sizeof…(ArgsT)==0)
    内联常用变量spaceshipHelper([[maybe_unused]]const QVariant&pLeft,[[maybe_unused]]const QVariant&pRight)不例外
    {
    std::terminate();//变量类型与任何给定模板类型都不匹配
    }
    模板
    内联通用变量spaceshipHelper(常量QVariant&pLeft、常量QVariant&pRight)无例外
    {
    if(pLeft.type()==静态转换(qMetaTypeId())
    {
    返回(pLeft.value()pRight.value());
    }
    返回variantSpaceshipHelper(pLeft,pRight);
    }
    模板
    内联自动变量空间(常量QVariant&pLeft、常量QVariant&pRight)无例外
    {
    使用CommonT=std::common\u type\u t;
    返回variantSpaceshipHelper(pLeft,pRight);
    }
    内联自动运算符(常量QVariant&pLeft、常量QVariant&pRight)无例外
    {
    断言(pLeft.type()==pRight.type());
    返回变量空间(pLeft、pRight);
    }
    

    可以很容易地将其他类型添加到
    variantSpaceship
    调用中。

    对于这两种类型来说,听起来是一个非常好的解决方案。但是,我的现实生活函数将有几十种类型。那将是一片混乱,对吗?@硅癌:怎么会这样?您需要列出您的类型以及如何提取它们,我已经为您设置了如何使多个案例易于阅读。如果你有这样的倾向,你可以把逻辑放到一个变量函数中,在这里你可以有效地向访问器lmbda finction拼写出类型,并且在那里有逻辑,但是它会分解成同样的倾向,是的;-)嗯,我也想到了使用变量函数。我想这可能是一个更好的解决方案。@Silicomancer实际上取决于你如何旋转它。就这个用例而言,它需要更多的键入和模糊处理。如果您有一个合适的二进制
    apply()
    函数,该函数将操作(在本例中为
    [](auto const&l,auto const&r)(return l r;}
    )作为参数,那么它可能有点不错:
    return apply(op,l,r);
    但是,编写
    apply()
    虽然机械,但有点烦人。看看我的答案,你觉得怎么样?
    不需要表现得对称吗?只打开
    l.type()
    就违反了这个属性。@Bergi你是对的。这就是为什么在我的真实代码中我要检查equa