顺序推导不一致的三向比较运算符
Three-way comparison operator with inconsistent ordering deduction
前段时间我定义了我的第一个三向比较运算符。它比较单一类型并替换多个常规运算符。很棒的功能。然后我尝试实现一个类似的运算符来通过委托比较两个变体:
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;
}
}
编译失败,出现错误
inconsistent deduction for auto return type: ‘std::strong_ordering’ and then ‘std::partial_ordering’.
显然int
和double
飞船操作员return不同类型。
解决这个问题的正确方法是什么?
您解析 returns auto
的任何其他函数的方式相同,其中不同的 return
语句推导不同。你要么:
- 确保所有
return
的类型相同,或者
- 明确选择 return 类型。
在这种情况下,int
s 比较为 strong_ordering
,而 double
s 比较为 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());
这给了你一个函数 returning partial_ordering
.
如果您想 return strong_ordering
,则必须将 double
比较提升到更高的类别。您可以通过两种方式做到这一点:
您可以使用 std::strong_order
,这是一个更昂贵的操作,但提供了对所有浮点值的总排序。然后你会写:
case QMetaType::Double:
return std::strong_order(l.toDouble(), r.toDouble());
或者您可以考虑 NaN
格式错误并以某种方式将其丢弃:
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;
}
}
这比较乏味,但我不确定是否有更直接的方法来进行这种提升。
int
和 double
的 operator<=>
类型不同,但它们应该具有共同的类型。您可能希望利用编译器自动查找正确的类型。您 可以 使用 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;
}
我尝试了一些模板代码来实现 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
调用中。
前段时间我定义了我的第一个三向比较运算符。它比较单一类型并替换多个常规运算符。很棒的功能。然后我尝试实现一个类似的运算符来通过委托比较两个变体:
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;
}
}
编译失败,出现错误
inconsistent deduction for auto return type: ‘std::strong_ordering’ and then ‘std::partial_ordering’.
显然int
和double
飞船操作员return不同类型。
解决这个问题的正确方法是什么?
您解析 returns auto
的任何其他函数的方式相同,其中不同的 return
语句推导不同。你要么:
- 确保所有
return
的类型相同,或者 - 明确选择 return 类型。
在这种情况下,int
s 比较为 strong_ordering
,而 double
s 比较为 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());
这给了你一个函数 returning partial_ordering
.
如果您想 return strong_ordering
,则必须将 double
比较提升到更高的类别。您可以通过两种方式做到这一点:
您可以使用 std::strong_order
,这是一个更昂贵的操作,但提供了对所有浮点值的总排序。然后你会写:
case QMetaType::Double:
return std::strong_order(l.toDouble(), r.toDouble());
或者您可以考虑 NaN
格式错误并以某种方式将其丢弃:
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;
}
}
这比较乏味,但我不确定是否有更直接的方法来进行这种提升。
int
和 double
的 operator<=>
类型不同,但它们应该具有共同的类型。您可能希望利用编译器自动查找正确的类型。您 可以 使用 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;
}
我尝试了一些模板代码来实现 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
调用中。