summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2023-09-29 20:36:31 +0200
committerIvan Solovev <ivan.solovev@qt.io>2023-12-07 23:36:14 +0100
commit3d231e27a8c38f8e840e161e76be3a153d6b0aa9 (patch)
tree4e66c98abd30052ab9672b1696797e63607a4e38
parent7cb25eb33c7875c913b4cb0154afd741e602d8aa (diff)
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>
-rw-r--r--src/corelib/global/qcompare.cpp59
-rw-r--r--src/corelib/global/qcompare.h77
-rw-r--r--tests/auto/corelib/global/qcompare/tst_qcompare.cpp113
3 files changed, 247 insertions, 2 deletions
diff --git a/src/corelib/global/qcompare.cpp b/src/corelib/global/qcompare.cpp
index 4f3a240068..91124c3d7d 100644
--- a/src/corelib/global/qcompare.cpp
+++ b/src/corelib/global/qcompare.cpp
@@ -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
diff --git a/src/corelib/global/qcompare.h b/src/corelib/global/qcompare.h
index a9227c71d3..8f3a99953c 100644
--- a/src/corelib/global/qcompare.h
+++ b/src/corelib/global/qcompare.h
@@ -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
diff --git a/tests/auto/corelib/global/qcompare/tst_qcompare.cpp b/tests/auto/corelib/global/qcompare/tst_qcompare.cpp
index ba382d57fb..f07b2b8910 100644
--- a/tests/auto/corelib/global/qcompare/tst_qcompare.cpp
+++ b/tests/auto/corelib/global/qcompare/tst_qcompare.cpp
@@ -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"