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