Long live qCompareThreeWay()

qCompareThreeWay() is a top-level wrapper around the helper
three-way comparison methods, which is mostly convenient for
generic client code.

When implementing compareThreeWay() for Qt types, we normally
provide the implementation only for (LeftType, RightType) pair,
but not the reversed one.
However, it is expected that qCompareThreeWay() would be available
for both combinations, because the reversed result can be easily
calculated.
Solve it by providing a helper hasCompareThreeWay<LT, RT> variable
and branching the implementation based on its value.

The noexcept check is inspired by the old implementation of qSwap().

[ChangeLog][QtCore] Added qCompareThreeWay() as a public API for
three-way comparison.

Task-number: QTBUG-104113
Change-Id: I6f24494d968c336f3dcdf620004b4190769cbdb2
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
This commit is contained in:
Ivan Solovev 2023-09-29 20:36:31 +02:00
parent 7cb25eb33c
commit 3d231e27a8
3 changed files with 247 additions and 2 deletions

View File

@ -134,6 +134,16 @@ CHECK(strong, equivalent);
This header introduces the \l Qt::partial_ordering, \l Qt::weak_ordering, and
\l Qt::strong_ordering types, which are Qt's C++17 backports of
\c {std::*_ordering} types.
This header also contains functions for implementing three-way comparison
in C++17.
The \c {Qt::compareThreeWay()} function overloads provide three-way
comparison for built-in C++ types.
The \l qCompareThreeWay() template serves as a generic three-way comparison
implementation. It relies on \c {Qt::compareThreeWay()} and free
\c {compareThreeWay()} functions in its implementation.
*/
/*!
@ -1250,4 +1260,53 @@ CHECK(strong, equivalent);
between \a lhs and \a rhs.
*/
/*!
\fn template <typename LeftType, typename RightType> qCompareThreeWay(const LeftType &lhs, const RightType &rhs)
\since 6.7
\relates <QtCompare>
Performs the three-way comparison on \a lhs and \a rhs and returns one of
the Qt ordering types as a result. This function is available for both
C++17 and C++20.
The actual returned type depends on \c LeftType and \c RightType.
\note This function template is only available when \c {compareThreeWay()}
is implemented for the \c {(LeftType, RightType)} pair or the reversed
\c {(RightType, LeftType)} pair.
This method is equivalent to
\code
using Qt::compareThreeWay;
return compareThreeWay(lhs, rhs);
\endcode
where \c {Qt::compareThreeWay} is the Qt implementation of three-way
comparison for built-in types.
The free \c {compareThreeWay} functions should provide three-way comparison
for custom types. The functions should return one of the Qt ordering types.
Qt provides \c {compareThreeWay} implementation for some of its types.
\note \b {Do not} re-implement \c {compareThreeWay()} for Qt types, as more
Qt types will get support for it in future Qt releases.
Use this function primarly in generic code, when you know nothing about
\c LeftType and \c RightType.
If you know the types, use
\list
\li \c {Qt::compareThreeWay} for built-in types
\li \c {compareThreeWay} for custom types
\endlist
Use \c {operator<=>()} directly in code that will only be compiled with
C++20 or later.
\sa Qt::partial_ordering, Qt::weak_ordering, Qt::strong_ordering
*/
QT_END_NAMESPACE

View File

@ -682,9 +682,82 @@ inline constexpr strong_ordering strong_ordering::greater(QtPrivate::Ordering::G
} // namespace Qt
QT_END_NAMESPACE
QT_BEGIN_INCLUDE_NAMESPACE
// This is intentionally included in the end of qcompare.h
// This is intentionally included after Qt::*_ordering types and before
// qCompareThreeWay. Do not change!
#include <QtCore/qcomparehelpers.h>
QT_END_INCLUDE_NAMESPACE
namespace QtPrivate {
namespace CompareThreeWayTester {
using Qt::compareThreeWay;
// Check if compareThreeWay is implemented for the (LT, RT) argument
// pair.
template <typename LT, typename RT, typename = void>
constexpr bool hasCompareThreeWay = false;
template <typename LT, typename RT>
constexpr bool hasCompareThreeWay<
LT, RT, std::void_t<decltype(compareThreeWay(std::declval<LT>(), std::declval<RT>()))>
> = true;
// Check if the operation is noexcept. We have two different overloads,
// depending on the available compareThreeWay() implementation.
// Both are declared, but not implemented. To be used only in unevaluated
// context.
template <typename LT, typename RT,
std::enable_if_t<hasCompareThreeWay<LT, RT>, bool> = true>
constexpr bool compareThreeWayNoexcept() noexcept
{ return noexcept(compareThreeWay(std::declval<LT>(), std::declval<RT>())); }
template <typename LT, typename RT,
std::enable_if_t<!hasCompareThreeWay<LT, RT> && hasCompareThreeWay<RT, LT>,
bool> = true>
constexpr bool compareThreeWayNoexcept() noexcept
{ return noexcept(compareThreeWay(std::declval<RT>(), std::declval<LT>())); }
} // namespace CompareThreeWayTester
} // namespace QtPrivate
#if defined(Q_QDOC)
template <typename LeftType, typename RightType>
auto qCompareThreeWay(const LeftType &lhs, const RightType &rhs);
#else
template <typename LT, typename RT,
std::enable_if_t<QtPrivate::CompareThreeWayTester::hasCompareThreeWay<LT, RT>
|| QtPrivate::CompareThreeWayTester::hasCompareThreeWay<RT, LT>,
bool> = true>
auto qCompareThreeWay(const LT &lhs, const RT &rhs)
noexcept(QtPrivate::CompareThreeWayTester::compareThreeWayNoexcept<LT, RT>())
{
using Qt::compareThreeWay;
if constexpr (QtPrivate::CompareThreeWayTester::hasCompareThreeWay<LT, RT>) {
return compareThreeWay(lhs, rhs);
} else {
const auto retval = compareThreeWay(rhs, lhs);
// We can compare any ordering type with Qt::partial_ordering, but we
// always need to return the right type. Use Qt::strong_ordering for
// casting, as it can be cast to any ordering type.
if (retval == Qt::partial_ordering::less)
return static_cast<decltype(retval)>(Qt::strong_ordering::greater);
else if (retval == Qt::partial_ordering::greater)
return static_cast<decltype(retval)>(Qt::strong_ordering::less);
return retval;
}
}
#endif // defined(Q_QDOC)
QT_END_NAMESPACE
#endif // QCOMPARE_H

View File

@ -20,6 +20,7 @@ private slots:
void strongOrdering();
void conversions();
void is_eq_overloads();
void compareThreeWay();
};
void tst_QCompare::legacyPartialOrdering()
@ -647,5 +648,117 @@ void tst_QCompare::is_eq_overloads()
#endif // __cpp_lib_three_way_comparison
}
class StringWrapper
{
public:
explicit StringWrapper() {}
explicit StringWrapper(const QString &val) : m_val(val) {}
QString value() const { return m_val; }
private:
static Qt::weak_ordering compareHelper(const QString &lhs, const QString &rhs) noexcept
{
const int res = QString::compare(lhs, rhs, Qt::CaseInsensitive);
if (res < 0)
return Qt::weak_ordering::less;
else if (res > 0)
return Qt::weak_ordering::greater;
else
return Qt::weak_ordering::equivalent;
}
friend bool comparesEqual(const StringWrapper &lhs, const StringWrapper &rhs) noexcept
{ return QString::compare(lhs.m_val, rhs.m_val, Qt::CaseInsensitive) == 0; }
friend Qt::weak_ordering
compareThreeWay(const StringWrapper &lhs, const StringWrapper &rhs) noexcept
{ return compareHelper(lhs.m_val, rhs.m_val); }
Q_DECLARE_WEAKLY_ORDERED(StringWrapper)
// these helper functions are intentionally non-noexcept
friend bool comparesEqual(const StringWrapper &lhs, int rhs)
{ return comparesEqual(lhs, StringWrapper(QString::number(rhs))); }
friend Qt::weak_ordering compareThreeWay(const StringWrapper &lhs, int rhs)
{ return compareHelper(lhs.m_val, QString::number(rhs)); }
Q_DECLARE_WEAKLY_ORDERED(StringWrapper, int)
QString m_val;
};
void tst_QCompare::compareThreeWay()
{
// test noexcept
// for custom types
static_assert(noexcept(qCompareThreeWay(std::declval<StringWrapper>(),
std::declval<StringWrapper>())));
static_assert(!noexcept(qCompareThreeWay(std::declval<StringWrapper>(),
std::declval<int>())));
static_assert(!noexcept(qCompareThreeWay(std::declval<int>(),
std::declval<StringWrapper>())));
// for built-in types
static_assert(noexcept(qCompareThreeWay(std::declval<int>(), std::declval<int>())));
static_assert(noexcept(qCompareThreeWay(std::declval<float>(), std::declval<int>())));
static_assert(noexcept(qCompareThreeWay(std::declval<double>(), std::declval<float>())));
static_assert(noexcept(qCompareThreeWay(std::declval<int>(), std::declval<int>())));
// enums
enum TestEnum : int {
Smaller,
Bigger
};
static_assert(noexcept(qCompareThreeWay(std::declval<TestEnum>(), std::declval<TestEnum>())));
// pointers
static_assert(noexcept(qCompareThreeWay(std::declval<StringWrapper *>(),
std::declval<StringWrapper *>())));
static_assert(noexcept(qCompareThreeWay(std::declval<StringWrapper *>(), nullptr)));
// Test some actual comparison results
// for custom types
QCOMPARE_EQ(qCompareThreeWay(StringWrapper("ABC"), StringWrapper("abc")),
Qt::weak_ordering::equivalent);
QVERIFY(StringWrapper("ABC") == StringWrapper("abc"));
QCOMPARE_EQ(qCompareThreeWay(StringWrapper("ABC"), StringWrapper("qwe")),
Qt::weak_ordering::less);
QVERIFY(StringWrapper("ABC") != StringWrapper("qwe"));
QCOMPARE_EQ(qCompareThreeWay(StringWrapper("qwe"), StringWrapper("ABC")),
Qt::weak_ordering::greater);
QVERIFY(StringWrapper("qwe") != StringWrapper("ABC"));
QCOMPARE_EQ(qCompareThreeWay(StringWrapper("10"), 10), Qt::weak_ordering::equivalent);
QVERIFY(StringWrapper("10") == 10);
QCOMPARE_EQ(qCompareThreeWay(StringWrapper("10"), 12), Qt::weak_ordering::less);
QVERIFY(StringWrapper("10") != 12);
QCOMPARE_EQ(qCompareThreeWay(StringWrapper("12"), 10), Qt::weak_ordering::greater);
QVERIFY(StringWrapper("12") != 10);
// reversed compareThreeWay()
auto result = qCompareThreeWay(10, StringWrapper("12"));
QCOMPARE_EQ(result, Qt::weak_ordering::less);
static_assert(std::is_same_v<decltype(result), Qt::weak_ordering>);
QVERIFY(10 != StringWrapper("12"));
result = qCompareThreeWay(12, StringWrapper("10"));
QCOMPARE_EQ(result, Qt::weak_ordering::greater);
static_assert(std::is_same_v<decltype(result), Qt::weak_ordering>);
QVERIFY(12 != StringWrapper("10"));
result = qCompareThreeWay(10, StringWrapper("10"));
QCOMPARE_EQ(result, Qt::weak_ordering::equivalent);
static_assert(std::is_same_v<decltype(result), Qt::weak_ordering>);
QVERIFY(10 == StringWrapper("10"));
// built-in types
QCOMPARE_EQ(qCompareThreeWay(1, 1.0), Qt::partial_ordering::equivalent);
QCOMPARE_EQ(qCompareThreeWay(1, 2), Qt::strong_ordering::less);
QCOMPARE_EQ(qCompareThreeWay(2.0f, 1.0), Qt::partial_ordering::greater);
// enums
QCOMPARE_EQ(qCompareThreeWay(Smaller, Bigger), Qt::strong_ordering::less);
// pointers
std::array<int, 2> arr{1, 0};
QCOMPARE_EQ(qCompareThreeWay(&arr[1], &arr[0]), Qt::strong_ordering::greater);
QCOMPARE_EQ(qCompareThreeWay(arr.data(), &arr[0]), Qt::strong_ordering::equivalent);
}
QTEST_MAIN(tst_QCompare)
#include "tst_qcompare.moc"