From 2f8f274caeeaae64880b99927ec0b729fcb0577d Mon Sep 17 00:00:00 2001 From: Ivan Solovev Date: Thu, 1 Aug 2024 19:41:39 +0200 Subject: [PATCH] Add QtOrderingPrivate::lexicographicalCompareThreeWay This function should behave similarly to std::lexicographical_compare_three_way, but be available in C++17. It is required at least to properly implement the compareThreeWay() helper function for Qt container types. The function requires that the contained types of the compared ranges provide a compareThreeWay() helper function. Similarly to std implementation, this patch also adds an overload that takes a custom comparator object. For now the functions are added in a private namespace, because they are only required to implement relational operators on Qt containers. We might want to expose them as public API later, if needed. Task-number: QTBUG-127095 Task-number: QTBUG-120305 Change-Id: I5b29129905b2e801ae7e470c96a7ef71e7b210d6 Reviewed-by: Marc Mutz --- src/corelib/global/qcompare.cpp | 36 ++ src/corelib/global/qcomparehelpers.h | 88 ++++ .../qcomparehelpers/tst_qcomparehelpers.h | 36 ++ .../qcomparehelpers/tst_qcomparehelpers1.cpp | 406 ++++++++++++++++++ 4 files changed, 566 insertions(+) diff --git a/src/corelib/global/qcompare.cpp b/src/corelib/global/qcompare.cpp index f80a08fa1f1..bac725fe6a2 100644 --- a/src/corelib/global/qcompare.cpp +++ b/src/corelib/global/qcompare.cpp @@ -1563,6 +1563,42 @@ CHECK(strong, equivalent); \sa Qt::partial_ordering, Qt::weak_ordering, Qt::strong_ordering */ +/*! + \fn template QtOrderingPrivate::lexicographicalCompareThreeWay(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) + \internal + \relates + + \brief Three-way lexicographic comparison of ranges. + + Checks how the range [ \a first1, \a last1 ) compares to the second range + [ \a first2, \a last2 ) and produces a result of the strongest applicable + category type. + + This function can only be used if \c InputIt1::value_type and + \c InputIt2::value_type types provide a \c {compareThreeWay()} helper method + that returns one of the Qt ordering types. + + \sa {Comparison types overview} +*/ + +/*! + \fn template QtOrderingPrivate::lexicographicalCompareThreeWay(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Comparator cmp) + \internal + \relates + \overload + + This overload takes a custom \c Comparator that is used to do the comparison. + The comparator should have the following signature: + + \badcode + OrderingType cmp(const InputIt1::value_type &lhs, const InputIt2::value_type &rhs); + \endcode + + where \c OrderingType is one of the Qt ordering types. + + \sa {Comparison types overview} +*/ + /*! \class Qt::totally_ordered_wrapper \inmodule QtCore diff --git a/src/corelib/global/qcomparehelpers.h b/src/corelib/global/qcomparehelpers.h index 9aadaab699c..b2282b2282d 100644 --- a/src/corelib/global/qcomparehelpers.h +++ b/src/corelib/global/qcomparehelpers.h @@ -1195,6 +1195,9 @@ namespace CompareThreeWayTester { using Qt::compareThreeWay; +template +using WrappedType = std::conditional_t, Qt::totally_ordered_wrapper, T>; + // Check if compareThreeWay is implemented for the (LT, RT) argument // pair. template @@ -1205,6 +1208,13 @@ constexpr inline bool hasCompareThreeWay< LT, RT, std::void_t(), std::declval()))> > = true; +template +constexpr inline bool hasCompareThreeWay< + LT*, RT*, + std::void_t>(), + std::declval>()))> + > = 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 @@ -1223,8 +1233,86 @@ constexpr bool compareThreeWayNoexcept() noexcept } // namespace CompareThreeWayTester +// These checks do not use Qt::compareThreeWay(), so only work for user-defined +// compareThreeWay() helper functions. +// We cannot use the same condition as in CompareThreeWayTester::hasCompareThreeWay, +// because GCC seems to cache and re-use the result. +// Created https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117174 +// For now, modify the condition a bit without changing its meaning. +template +struct HasCustomCompareThreeWay : std::false_type {}; + +template +struct HasCustomCompareThreeWay< + LT, RT, + std::void_t(), std::declval())))> + > : std::true_type {}; + +template +auto lexicographicalCompareThreeWay(InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2, + Compare cmp) +{ + using R = decltype(cmp(*first1, *first2)); + + while (first1 != last1) { + if (first2 == last2) + return R::greater; + const auto r = cmp(*first1, *first2); + if (is_neq(r)) + return r; + ++first1; + ++first2; + } + return first2 == last2 ? R::equivalent : R::less; +} + +template +auto lexicographicalCompareThreeWay(InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2) +{ + using LT = typename std::iterator_traits::value_type; + using RT = typename std::iterator_traits::value_type; + + // if LT && RT are pointers, and there is no user-defined compareThreeWay() + // operation for the pointers, we need to wrap them into + // Qt::totally_ordered_wrapper. + constexpr bool UseWrapper = + std::conjunction_v, std::is_pointer, + std::negation>, + std::negation>>; + using WrapLT = std::conditional_t, + const LT &>; + using WrapRT = std::conditional_t, + const RT &>; + + auto cmp = [](LT const &lhs, RT const &rhs) { + using Qt::compareThreeWay; + namespace Test = QtOrderingPrivate::CompareThreeWayTester; + // Need this because the user might provide only + // compareThreeWay(LT, RT), but not the reversed version. + if constexpr (Test::hasCompareThreeWay) + return compareThreeWay(WrapLT(lhs), WrapRT(rhs)); + else + return QtOrderingPrivate::reversed(compareThreeWay(WrapRT(rhs), WrapLT(lhs))); + }; + return lexicographicalCompareThreeWay(first1, last1, first2, last2, cmp); +} + } // namespace QtOrderingPrivate +namespace Qt { + +template +using if_has_qt_compare_three_way = + std::enable_if_t + || QtOrderingPrivate::CompareThreeWayTester::hasCompareThreeWay, + bool>; + +} // namespace Qt + QT_END_NAMESPACE namespace std { diff --git a/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.h b/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.h index aac786ba43d..b892cd33362 100644 --- a/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.h +++ b/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.h @@ -66,6 +66,42 @@ private Q_SLOTS: void totallyOrderedWrapperBasics(); void compareAutoReturnType(); + +private: + template + void lexicographicalCompareThreeWayDataImpl(); + + template + void lexicographicalCompareThreeWayImpl(); + + template + void lexicographicalCompareThreeWayComparatorImpl(); + +private slots: + void lexicographicalCompareThreeWay_ThreeWayCmp_Int_data(); + void lexicographicalCompareThreeWay_ThreeWayCmp_Int(); + void lexicographicalCompareThreeWay_ThreeWayCmp_Float_data(); + void lexicographicalCompareThreeWay_ThreeWayCmp_Float(); + void lexicographicalCompareThreeWay_ThreeWayCmp_Weak_data(); + void lexicographicalCompareThreeWay_ThreeWayCmp_Weak(); + + void lexicographicalCompareThreeWay_Mixed_Int_data(); + void lexicographicalCompareThreeWay_Mixed_Int(); + void lexicographicalCompareThreeWay_Mixed_Float_data(); + void lexicographicalCompareThreeWay_Mixed_Float(); + void lexicographicalCompareThreeWay_Mixed_Weak_data(); + void lexicographicalCompareThreeWay_Mixed_Weak(); + + void lexicographicalCompareThreeWay_Comparator_Int_data(); + void lexicographicalCompareThreeWay_Comparator_Int(); + void lexicographicalCompareThreeWay_Comparator_Float_data(); + void lexicographicalCompareThreeWay_Comparator_Float(); + void lexicographicalCompareThreeWay_Comparator_Weak_data(); + void lexicographicalCompareThreeWay_Comparator_Weak(); + + void lexicographicalCompareThreeWay_Pointers(); + void lexicographicalCompareThreeWay_NonCopyMove(); + void lexicographicalCompareThreeWay_CustomPointerHelper(); }; #endif // TST_QCOMPAREHELPERS_H diff --git a/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers1.cpp b/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers1.cpp index 118b61df482..677d913f106 100644 --- a/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers1.cpp +++ b/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers1.cpp @@ -198,3 +198,409 @@ void tst_QCompareHelpers::compareAutoReturnType() QTestPrivate::testAllComparisonOperatorsCompile(); } } + +template +class LessOnly +{ +public: + using Type = T; + + LessOnly(T v) : val(v) {} + T value() const { return val; } +private: + friend bool operator<(LessOnly lhs, LessOnly rhs) + { return lhs.val < rhs.val; } + + T val; +}; + +template +class ThreeWayCmp +{ +public: + using Type = T; + + ThreeWayCmp(T v) : val(v) {} +private: + template + friend bool operator==(ThreeWayCmp lhs, ThreeWayCmp rhs) + { return lhs.val == rhs.val; } + template + friend bool operator==(ThreeWayCmp lhs, LessOnly rhs) + { return lhs.val == rhs.value(); } + template + friend auto compareThreeWay(ThreeWayCmp lhs, ThreeWayCmp rhs) + { + using Qt::compareThreeWay; + return compareThreeWay(lhs.val, rhs.val); + } + template + friend auto compareThreeWay(ThreeWayCmp lhs, LessOnly rhs) + { + using Qt::compareThreeWay; + return compareThreeWay(lhs.val, rhs.value()); + } + + T val; +}; + +// A dummy int-based class to implement weak ordering +struct Weak +{ + int val; +private: + friend constexpr bool comparesEqual(Weak lhs, Weak rhs) noexcept + { return lhs.val == rhs.val; } + friend constexpr Qt::weak_ordering compareThreeWay(Weak lhs, Weak rhs) noexcept + { + const auto r = Qt::compareThreeWay(lhs.val, rhs.val); + return Qt::weak_ordering{r}; + } + Q_DECLARE_WEAKLY_ORDERED_LITERAL_TYPE(Weak) +}; + +template +void tst_QCompareHelpers::lexicographicalCompareThreeWayDataImpl() +{ + QTest::addColumn>("lhs"); + QTest::addColumn>("rhs"); + QTest::addColumn("expectedResult"); + + using LT = typename LeftType::Type; + using RT = typename RightType::Type; + + constexpr bool HasOnlyOpLess = std::is_same_v> + && std::is_same_v>; + + if constexpr (!HasOnlyOpLess) + static_assert(std::is_same_v, bool>); + + QTest::addRow("empty") + << QList{} + << QList{} + << OrderingType::equivalent; + QTest::addRow("same_length_equal") + << QList{LT{1}, LT{2}, LT{3}} + << QList{RT{1}, RT{2}, RT{3}} + << OrderingType::equivalent; + QTest::addRow("greater_val") + << QList{LT{1}, LT{3}, LT{2}} + << QList{RT{1}, RT{2}, RT{3}, RT{4}} + << OrderingType::greater; + QTest::addRow("less_val") + << QList{LT{1}, LT{2}, LT{3}, LT{4}} + << QList{RT{1}, RT{3}, RT{2}} + << OrderingType::less; + QTest::addRow("greater_length") + << QList{LT{1}, LT{2}, LT{3}} + << QList{RT{1}, RT{2}} + << OrderingType::greater; + QTest::addRow("less_length") + << QList{LT{1}, LT{2}, LT{3}} + << QList{RT{1}, RT{2}, RT{3}, RT{4}} + << OrderingType::less; + + if constexpr (!HasOnlyOpLess && std::is_same_v) { + const float nan = std::numeric_limits::quiet_NaN(); + QTest::addRow("unordered") + << QList{LT{nan}, LT{2}, LT{3}} + << QList{RT{1}, RT{2}, RT{3}} + << OrderingType::unordered; + QTest::addRow("unordered_start_with_nan") + << QList{LT{nan}, LT{2}, LT{3}} + << QList{RT{nan}, RT{3}, RT{2}} + << OrderingType::unordered; + } +} + +template +void tst_QCompareHelpers::lexicographicalCompareThreeWayImpl() +{ + QFETCH(const QList, lhs); + QFETCH(const QList, rhs); + QFETCH(const OrderingType, expectedResult); + + const auto res = + QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.begin(), lhs.end(), + rhs.begin(), rhs.end()); + QCOMPARE_EQ(res, expectedResult); + + // check the C-style arrays as well + const auto rawRes = + QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.data(), lhs.data() + lhs.size(), + rhs.data(), rhs.data() + rhs.size()); + QCOMPARE_EQ(rawRes, expectedResult); +} + +template +Qt::strong_ordering lessOnlyCmp(LT const &lhs, RT const &rhs) +{ + if (std::less<>{}(lhs, rhs)) + return Qt::strong_ordering::less; + if (std::less<>{}(rhs, lhs)) + return Qt::strong_ordering::greater; + return Qt::strong_ordering::equivalent; +} + +template +void tst_QCompareHelpers::lexicographicalCompareThreeWayComparatorImpl() +{ + QFETCH(const QList, lhs); + QFETCH(const QList, rhs); + QFETCH(const OrderingType, expectedResult); + + // free function + { + const auto res = + QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + lessOnlyCmp); + QCOMPARE_EQ(res, expectedResult); + + // check the C-style arrays as well + const auto rawRes = + QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.data(), lhs.data() + lhs.size(), + rhs.data(), rhs.data() + rhs.size(), + lessOnlyCmp); + QCOMPARE_EQ(rawRes, expectedResult); + } + // lambda + { + auto cmp = [](LeftType const &lhs, RightType const &rhs) { + return lessOnlyCmp(lhs, rhs); + }; + const auto res = + QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + cmp); + QCOMPARE_EQ(res, expectedResult); + + // check the C-style arrays as well + const auto rawRes = + QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.data(), lhs.data() + lhs.size(), + rhs.data(), rhs.data() + rhs.size(), + std::move(cmp)); + QCOMPARE_EQ(rawRes, expectedResult); + } +} + +void tst_QCompareHelpers::lexicographicalCompareThreeWay_ThreeWayCmp_Int_data() +{ + lexicographicalCompareThreeWayDataImpl, ThreeWayCmp, + Qt::strong_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_ThreeWayCmp_Int() +{ + lexicographicalCompareThreeWayImpl, ThreeWayCmp, + Qt::strong_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_ThreeWayCmp_Float_data() +{ + lexicographicalCompareThreeWayDataImpl, ThreeWayCmp, + Qt::partial_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_ThreeWayCmp_Float() +{ + lexicographicalCompareThreeWayImpl, ThreeWayCmp, + Qt::partial_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_ThreeWayCmp_Weak_data() +{ + lexicographicalCompareThreeWayDataImpl, ThreeWayCmp, + Qt::weak_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_ThreeWayCmp_Weak() +{ + lexicographicalCompareThreeWayImpl, ThreeWayCmp, + Qt::weak_ordering>(); +} + +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Mixed_Int_data() +{ + lexicographicalCompareThreeWayDataImpl, ThreeWayCmp, + Qt::strong_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Mixed_Int() +{ + lexicographicalCompareThreeWayImpl, ThreeWayCmp, + Qt::strong_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Mixed_Float_data() +{ + lexicographicalCompareThreeWayDataImpl, ThreeWayCmp, + Qt::partial_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Mixed_Float() +{ + lexicographicalCompareThreeWayImpl, ThreeWayCmp, + Qt::partial_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Mixed_Weak_data() +{ + lexicographicalCompareThreeWayDataImpl, ThreeWayCmp, + Qt::weak_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Mixed_Weak() +{ + lexicographicalCompareThreeWayImpl, ThreeWayCmp, + Qt::weak_ordering>(); +} + +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Comparator_Int_data() +{ + lexicographicalCompareThreeWayDataImpl, LessOnly, Qt::strong_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Comparator_Int() +{ + lexicographicalCompareThreeWayComparatorImpl, LessOnly, + Qt::strong_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Comparator_Float_data() +{ + lexicographicalCompareThreeWayDataImpl, LessOnly, + Qt::partial_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Comparator_Float() +{ + lexicographicalCompareThreeWayComparatorImpl, LessOnly, + Qt::partial_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Comparator_Weak_data() +{ + lexicographicalCompareThreeWayDataImpl, LessOnly, Qt::weak_ordering>(); +} +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Comparator_Weak() +{ + lexicographicalCompareThreeWayComparatorImpl, LessOnly, + Qt::weak_ordering>(); +} + +void tst_QCompareHelpers::lexicographicalCompareThreeWay_Pointers() +{ + constexpr std::array a = {42, 0, 17}; // the actual values do not matter + + { + const QList lhs{&a[0], &a[1], &a[2]}; + const QList rhs{&a[0], &a[1], &a[2]}; + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.begin(), lhs.end(), + rhs.begin(), rhs.end()); + QCOMPARE_EQ(res, Qt::strong_ordering::equivalent); + } + { + const QList lhs{&a[0], &a[1], &a[2]}; + const QList rhs{&a[0], &a[2]}; + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.begin(), lhs.end(), + rhs.begin(), rhs.end()); + QCOMPARE_EQ(res, Qt::strong_ordering::less); + } + { + const QList lhs{&a[0], &a[2], &a[1]}; + const QList rhs{&a[0], &a[1], &a[2]}; + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.begin(), lhs.end(), + rhs.begin(), rhs.end()); + QCOMPARE_EQ(res, Qt::strong_ordering::greater); + } + { + const QList lhs{&a[0], &a[1], &a[2], &a[0]}; + const QList rhs{&a[0], &a[1], &a[2]}; + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(lhs.begin(), lhs.end(), + rhs.begin(), rhs.end()); + QCOMPARE_EQ(res, Qt::strong_ordering::greater); + } +} + +struct NonCopyMove +{ + float val; + + constexpr NonCopyMove(float f) : val(f) {} + Q_DISABLE_COPY_MOVE(NonCopyMove) + +private: + friend auto compareThreeWay(const NonCopyMove &lhs, const NonCopyMove &rhs) + { + return Qt::compareThreeWay(lhs.val, rhs.val); + } +}; + +void tst_QCompareHelpers::lexicographicalCompareThreeWay_NonCopyMove() +{ + constexpr std::array a1 = {NonCopyMove{1.f}, NonCopyMove{2.f}, NonCopyMove{3.f}}; + constexpr std::array a2 = {NonCopyMove{1.f}, NonCopyMove{3.f}, NonCopyMove{2.f}}; + constexpr std::array a3 = {NonCopyMove{std::numeric_limits::quiet_NaN()}, + NonCopyMove{2.f}, NonCopyMove{3.f}}; + + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a1.begin(), a1.end(), + a1.begin(), a1.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::equivalent); + } + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a1.begin(), a1.end(), + a2.begin(), a2.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::less); + } + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a2.begin(), a2.end(), + a1.begin(), a1.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::greater); + } + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a1.begin(), a1.end(), + a3.begin(), a3.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::unordered); + } +} + +struct WithCustomPointerCompareThreeWayHelper +{ + float val; + +private: + friend auto compareThreeWay(const WithCustomPointerCompareThreeWayHelper *lhs, + const WithCustomPointerCompareThreeWayHelper *rhs) + { + if (lhs && rhs) + return Qt::compareThreeWay(lhs->val, rhs->val); + else if (lhs) + return Qt::partial_ordering::greater; + else + return Qt::partial_ordering::less; + } +}; + +static_assert(QtOrderingPrivate::HasCustomCompareThreeWay::value); +static_assert(!QtOrderingPrivate::HasCustomCompareThreeWay::value); + +void tst_QCompareHelpers::lexicographicalCompareThreeWay_CustomPointerHelper() +{ + const WithCustomPointerCompareThreeWayHelper val1{1.f}; + const WithCustomPointerCompareThreeWayHelper val2{2.f}; + const WithCustomPointerCompareThreeWayHelper val3{3.f}; + const WithCustomPointerCompareThreeWayHelper nan{std::numeric_limits::quiet_NaN()}; + + const std::array a1 = {&val1, &val2, &val3}; + const std::array a2 = {&val1, &val3, &val2}; + const std::array a3 = {&nan, &val2, &val3}; + + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a1.begin(), a1.end(), + a1.begin(), a1.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::equivalent); + } + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a1.begin(), a1.end(), + a2.begin(), a2.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::less); + } + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a2.begin(), a2.end(), + a1.begin(), a1.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::greater); + } + { + const auto res = QtOrderingPrivate::lexicographicalCompareThreeWay(a1.begin(), a1.end(), + a3.begin(), a3.end()); + QCOMPARE_EQ(res, Qt::partial_ordering::unordered); + } +}