From fe12650e9d85ea0ed4a73f85cdbef0ddf3b67ae3 Mon Sep 17 00:00:00 2001 From: Ivan Solovev Date: Tue, 2 May 2023 12:33:26 +0200 Subject: [PATCH] Implement compare helper macros These macros should unwrap into a proper set of equality and ordering operators, depending on the C++ standard being used. For C++17, all 6 operators (==, !=, <, >, <=, >=) are overloaded, while for C++20 only the overloads for opeartor==() and operator<=>() are provided. The macros are documented as internal for now. The macros rely on two helper functions: bool comparesEqual(LeftType lhs, RightType rhs); ReturnType compareThreeWay(LeftType lhs, RightType rhs); The comparesEqual() helper function is used to implement operator==() and operator!=(). The compareThreeWay() helper function is used to implement the four relational operators in C++17, or operator<=>() in C++20. ReturnType must be one of Qt::{partial,weak,strong}_ordering. When possible, the functions should also be declared constexpr and noexcept. It's the user's responsibility to provide the functions before using the macros. Implement a test case which applies the new macros to the dummy classes, and uses the new helper function to verify the comparison results. The MSVC compiler before version 19.36 has a bug, where it fails to correctly generate reverse opeerators in C++20 mode. Introduce a new Q_COMPILER_LACKS_THREE_WAY_COMPARE_SYMMETRY definition for such compiler versions, and use it to manually generate reversed operators when needed. Task-number: QTBUG-104113 Change-Id: Idc19d55df011fd616ff654f35a964e831b8ab93b Reviewed-by: Edward Welbourne Reviewed-by: Marc Mutz --- src/corelib/CMakeLists.txt | 1 + src/corelib/global/qcompare.cpp | 336 ++++++++++++ src/corelib/global/qcompare.h | 3 + src/corelib/global/qcomparehelpers.h | 307 +++++++++++ src/corelib/global/qcompilerdetection.h | 7 + src/testlib/qcomparisontesthelper_p.h | 19 +- tests/auto/corelib/global/CMakeLists.txt | 1 + .../global/qcomparehelpers/CMakeLists.txt | 9 + .../qcomparehelpers/tst_qcomparehelpers.cpp | 491 ++++++++++++++++++ 9 files changed, 1170 insertions(+), 4 deletions(-) create mode 100644 src/corelib/global/qcomparehelpers.h create mode 100644 tests/auto/corelib/global/qcomparehelpers/CMakeLists.txt create mode 100644 tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.cpp diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 827680cc4fc..8769e0c9eb1 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -50,6 +50,7 @@ qt_internal_add_module(Core global/qassert.cpp global/qassert.h global/qcompare_impl.h global/qcompare.cpp global/qcompare.h + global/qcomparehelpers.h global/qcompilerdetection.h global/qconstructormacros.h global/qcontainerinfo.h diff --git a/src/corelib/global/qcompare.cpp b/src/corelib/global/qcompare.cpp index 9275ff7a8ee..9e4d958d167 100644 --- a/src/corelib/global/qcompare.cpp +++ b/src/corelib/global/qcompare.cpp @@ -122,6 +122,20 @@ CHECK(strong, equivalent); implementing three-way comparison with C++17. */ +/*! + \headerfile + \inmodule QtCore + \title Classes and helpers for defining comparison operators + \keyword qtcompare + + \brief The header file defines \c {Qt::*_ordering} types and helper + macros for defining comparison operators. + + 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. +*/ + /*! \class Qt::strong_ordering \inmodule QtCore @@ -762,4 +776,326 @@ CHECK(strong, equivalent); with respect to the right operand. */ +/*! + \internal + \macro Q_DECLARE_EQUALITY_COMPARABLE(Type) + \macro Q_DECLARE_EQUALITY_COMPARABLE(LeftType, RightType) + \macro Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(Type) + \macro Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(LeftType, RightType) + \since 6.7 + \relates + + These macros are used to generate \c {operator==()} and \c {operator!=()}. + + In C++17 mode, the mixed-type overloads also generate the reversed + operators. + + In C++20 mode, only \c {operator==()} is defined. \c {operator!=()}, + as well as the reversed operators for mixed-type comparison, are synthesized + by the compiler. + + The operators are implemented in terms of a helper function + \c {comparesEqual()}. + It's the user's responsibility to declare and define this function. + + Consider the following example of a comparison operators declaration: + + \code + class MyClass { + ... + private: + friend bool comparesEqual(const MyClass &, const MyClass &) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(MyClass) + }; + \endcode + + When compiled with C++17, the macro will expand into the following code: + + \code + friend bool operator==(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + friend bool operator!=(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + \endcode + + When compiled with C++20, the macro will expand only into \c {operator==()}: + + \code + friend bool operator==(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + \endcode + + The \c {*_LITERAL_TYPE} versions of the macros are used to generate + \c constexpr operators. This means that the helper \c {comparesEqual()} + function must also be \c constexpr. + + Consider the following example of a mixed-type \c constexpr comparison + operators declaration: + + \code + class MyClass { + ... + private: + friend constexpr bool comparesEqual(const MyClass &, int) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(MyClass, int) + }; + \endcode + + When compiled with C++17, the macro will expand into the following code: + + \code + friend constexpr bool operator==(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + friend constexpr bool operator!=(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + friend constexpr bool operator==(int lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + friend constexpr bool operator!=(int lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + \endcode + + When compiled with C++20, the macro expands only into \c {operator==()}: + + \code + friend constexpr bool operator==(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + \endcode +*/ + +/*! + \internal + \macro Q_DECLARE_PARTIALLY_ORDERED(Type) + \macro Q_DECLARE_PARTIALLY_ORDERED(LeftType, RightType) + \macro Q_DECLARE_PARTIALLY_ORDERED_LITERAL_TYPE(Type) + \macro Q_DECLARE_PARTIALLY_ORDERED_LITERAL_TYPE(LeftType, RightType) + \since 6.7 + \relates + + These macros are used to generate all six relational operators. + The operators represent + \l {https://en.cppreference.com/w/cpp/utility/compare/partial_ordering} + {partial ordering}. + + These macros use respective overloads of the + \l {Q_DECLARE_EQUALITY_COMPARABLE} macro to generate \c {operator==()} and + \c {operator!=()}, and also generate the four relational operators: + \c {operator<()}, \c {operator>()}, \c {operator<=()}, and \c {operator>()}. + + In C++17 mode, the mixed-type overloads also generate the reversed + operators. + + In C++20 mode, only \c {operator==()} and \c {operator<=>()} are defined. + Other operators, as well as the reversed operators for mixed-type + comparison, are synthesized by the compiler. + + The (in)equality operators are implemented in terms of a helper function + \c {comparesEqual()}. The other relational operators are implemented in + terms of a helper function \c {compareThreeWay()}. + The \c {compareThreeWay()} function \e must return an object of type + \l Qt::partial_ordering. It's the user's responsibility to declare and define + both helper functions. + + Consider the following example of a comparison operators declaration: + + \code + class MyClass { + ... + private: + friend bool comparesEqual(const MyClass &, const MyClass &) noexcept; + friend Qt::partial_ordering compareThreeWay(const MyClass &, const MyClass &) noexcept; + Q_DECLARE_PARTIALLY_ORDERED(MyClass) + }; + \endcode + + When compiled with C++17, the macro will expand into the following code: + + \code + // operator==() and operator!=() are generated from + // Q_DECLARE_EQUALITY_COMPARABLE + friend bool operator<(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend bool operator>(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend bool operator<=(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend bool operator>=(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + \endcode + + When compiled with C++20, the macro will expand into \c {operator==()} and + \c {operator<=>()}: + + \code + friend bool operator==(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + friend std::partial_ordering + operator<=>(const MyClass &lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + \endcode + + The \c {*_LITERAL_TYPE} versions of the macros are used to generate + \c constexpr operators. This means that the helper \c {comparesEqual()} and + \c {compareThreeWay()} functions must also be \c constexpr. + + Consider the following example of a mixed-type \c constexpr comparison + operators declaration: + + \code + class MyClass { + ... + private: + friend constexpr bool comparesEqual(const MyClass &, int) noexcept; + friend constexpr Qt::partial_ordering compareThreeWay(const MyClass &, int) noexcept; + Q_DECLARE_PARTIALLY_ORDERED_LITERAL_TYPE(MyClass, int) + }; + \endcode + + When compiled with C++17, the macro will expand into the following code: + + \code + // operator==(), operator!=(), and their reversed versions are generated + // from Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE + friend constexpr bool operator<(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend constexpr bool operator>(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend constexpr bool operator<=(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend constexpr bool operator>=(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend constexpr bool operator<(int lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend constexpr bool operator>(int lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend constexpr bool operator<=(int lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + friend constexpr bool operator>=(int lhs, const MyClass &rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + \endcode + + When compiled with C++20, the macro will expand into \c {operator==()} and + \c {operator<=>()}: + + \code + friend constexpr bool operator==(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses comparesEqual() + } + friend constexpr std::partial_ordering + operator<=>(const MyClass &lhs, int rhs) noexcept + { + // inline implementation which uses compareThreeWay() + } + \endcode + + \sa Q_DECLARE_EQUALITY_COMPARABLE, Q_DECLARE_WEAKLY_ORDERED, + Q_DECLARE_STRONGLY_ORDERED +*/ + +/*! + \internal + \macro Q_DECLARE_WEAKLY_ORDERED(Type) + \macro Q_DECLARE_WEAKLY_ORDERED(LeftType, RightType) + \macro Q_DECLARE_WEAKLY_ORDERED_LITERAL_TYPE(Type) + \macro Q_DECLARE_WEAKLY_ORDERED_LITERAL_TYPE(LeftType, RightType) + \since 6.7 + \relates + + These macros behave similarly to the + \l {Q_DECLARE_PARTIALLY_ORDERED} overloads, but represent + \l {https://en.cppreference.com/w/cpp/utility/compare/weak_ordering} + {weak ordering}. + + The (in)equality operators are implemented in terms of a helper function + \c {comparesEqual()}. The other relational operators are implemented in + terms of a helper function \c {compareThreeWay()}. + The \c {compareThreeWay()} function \e must return an object of type + \l Qt::weak_ordering. It's the user's responsibility to declare and define both + helper functions. + + The \c {*_LITERAL_TYPE} overloads are used to generate \c constexpr + operators. This means that the helper \c {comparesEqual()} and + \c {compareThreeWay()} functions must also be \c constexpr. + + See \l {Q_DECLARE_PARTIALLY_ORDERED} for usage examples. + + \sa Q_DECLARE_PARTIALLY_ORDERED, Q_DECLARE_STRONGLY_ORDERED, + Q_DECLARE_EQUALITY_COMPARABLE +*/ + +/*! + \internal + \macro Q_DECLARE_STRONGLY_ORDERED(Type) + \macro Q_DECLARE_STRONGLY_ORDERED(LeftType, RightType) + \macro Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE(Type) + \macro Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE(LeftType, RightType) + \since 6.7 + \relates + + These macros behave similarly to the + \l {Q_DECLARE_PARTIALLY_ORDERED} overloads, but represent + \l {https://en.cppreference.com/w/cpp/utility/compare/strong_ordering} + {strong ordering}. + + The (in)equality operators are implemented in terms of a helper function + \c {comparesEqual()}. The other relational operators are implemented in + terms of a helper function \c {compareThreeWay()}. + The \c {compareThreeWay()} function \e must return an object of type + \l Qt::strong_ordering. It's the user's responsibility to declare and define + both helper functions. + + The \c {*_LITERAL_TYPE} overloads are used to generate \c constexpr + operators. This means that the helper \c {comparesEqual()} and + \c {compareThreeWay()} functions must also be \c constexpr. + + See \l {Q_DECLARE_PARTIALLY_ORDERED} for usage examples. + + \sa Q_DECLARE_PARTIALLY_ORDERED, Q_DECLARE_WEAKLY_ORDERED, + Q_DECLARE_EQUALITY_COMPARABLE +*/ + QT_END_NAMESPACE diff --git a/src/corelib/global/qcompare.h b/src/corelib/global/qcompare.h index 5be0a4877d9..a9227c71d31 100644 --- a/src/corelib/global/qcompare.h +++ b/src/corelib/global/qcompare.h @@ -684,4 +684,7 @@ inline constexpr strong_ordering strong_ordering::greater(QtPrivate::Ordering::G QT_END_NAMESPACE +// This is intentionally included in the end of qcompare.h +#include + #endif // QCOMPARE_H diff --git a/src/corelib/global/qcomparehelpers.h b/src/corelib/global/qcomparehelpers.h new file mode 100644 index 00000000000..f445e29b003 --- /dev/null +++ b/src/corelib/global/qcomparehelpers.h @@ -0,0 +1,307 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCOMPARE_H +#error "Do not include qcomparehelpers.h directly. Use qcompare.h instead." +#endif + +#ifndef QCOMPAREHELPERS_H +#define QCOMPAREHELPERS_H + +#if 0 +#pragma qt_no_master_include +#pragma qt_sync_skip_header_check +#pragma qt_sync_stop_processing +#endif + +#include + +#ifdef __cpp_lib_three_way_comparison +#include +#endif + +QT_BEGIN_NAMESPACE + +/* + For all the macros these parameter names are used: + * LeftType - the type of the left operand of the comparison + * RightType - the type of the right operand of the comparison + * Constexpr - must be either constexpr or empty. Defines whether the + operator is constexpr or not + + The macros require two helper functions. For operators to be constexpr, + these must be constexpr, too. Additionally, other attributes (like + Q__EXPORT, Q_DECL_CONST_FUNCTION, etc) can be applied to them. + Aside from that, their declaration should match: + bool comparesEqual(LeftType, RightType) noexcept; + ReturnType compareThreeWay(LeftType, RightType) noexcept; + + The ReturnType can be one of Qt::{partial,weak,strong}_ordering. The actual + type depends on the macro being used. + It makes sense to define the helper functions as hidden friends of the + class, so that they could be found via ADL, and don't participate in + unintended implicit conversions. +*/ + +// Seems that qdoc uses C++20 even when Qt is compiled in C++17 mode. +// Or at least it defines __cpp_lib_three_way_comparison. +// Let qdoc see only the C++17 operators for now, because that's what our docs +// currently describe. +#if defined(__cpp_lib_three_way_comparison) && !defined(Q_QDOC) +// C++20 - provide operator==() for equality, and operator<=>() for ordering + +#define QT_DECLARE_EQUALITY_OPERATORS_HELPER(LeftType, RightType, Constexpr) \ + friend Constexpr bool operator==(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(comparesEqual(lhs, rhs))) \ + { return comparesEqual(lhs, rhs); } + +#define QT_DECLARE_3WAY_HELPER_STRONG(LeftType, RightType, Constexpr) \ + friend Constexpr std::strong_ordering \ + operator<=>(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(compareThreeWay(lhs, rhs))) \ + { \ + return compareThreeWay(lhs, rhs); \ + } + +#define QT_DECLARE_3WAY_HELPER_WEAK(LeftType, RightType, Constexpr) \ + friend Constexpr std::weak_ordering \ + operator<=>(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(compareThreeWay(lhs, rhs))) \ + { \ + return compareThreeWay(lhs, rhs); \ + } + +#define QT_DECLARE_3WAY_HELPER_PARTIAL(LeftType, RightType, Constexpr) \ + friend Constexpr std::partial_ordering \ + operator<=>(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(compareThreeWay(lhs, rhs))) \ + { \ + return compareThreeWay(lhs, rhs); \ + } + +#define QT_DECLARE_ORDERING_OPERATORS_HELPER(OrderingType, LeftType, RightType, Constexpr) \ + QT_DECLARE_EQUALITY_OPERATORS_HELPER(LeftType, RightType, Constexpr) \ + QT_DECLARE_3WAY_HELPER_ ## OrderingType (LeftType, RightType, Constexpr) + +#ifdef Q_COMPILER_LACKS_THREE_WAY_COMPARE_SYMMETRY + +// define reversed versions of the operators manually, because buggy MSVC versions do not do it +#define QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(LeftType, RightType, Constexpr) \ + friend Constexpr bool operator==(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(comparesEqual(rhs, lhs))) \ + { return comparesEqual(rhs, lhs); } + +#define QT_DECLARE_REVERSED_3WAY_HELPER_STRONG(LeftType, RightType, Constexpr) \ + friend Constexpr std::strong_ordering \ + operator<=>(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(compareThreeWay(rhs, lhs))) \ + { \ + const auto r = compareThreeWay(rhs, lhs); \ + if (r > 0) return std::strong_ordering::less; \ + if (r < 0) return std::strong_ordering::greater; \ + return r; \ + } + +#define QT_DECLARE_REVERSED_3WAY_HELPER_WEAK(LeftType, RightType, Constexpr) \ + friend Constexpr std::weak_ordering \ + operator<=>(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(compareThreeWay(rhs, lhs))) \ + { \ + const auto r = compareThreeWay(rhs, lhs); \ + if (r > 0) return std::weak_ordering::less; \ + if (r < 0) return std::weak_ordering::greater; \ + return r; \ + } + +#define QT_DECLARE_REVERSED_3WAY_HELPER_PARTIAL(LeftType, RightType, Constexpr) \ + friend Constexpr std::partial_ordering \ + operator<=>(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(compareThreeWay(rhs, lhs))) \ + { \ + const auto r = compareThreeWay(rhs, lhs); \ + if (r > 0) return std::partial_ordering::less; \ + if (r < 0) return std::partial_ordering::greater; \ + return r; \ + } + +#define QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(OrderingString, LeftType, RightType, \ + Constexpr) \ + QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(LeftType, RightType, Constexpr) \ + QT_DECLARE_REVERSED_3WAY_HELPER_ ## OrderingString (LeftType, RightType, Constexpr) + +#else + +// dummy macros for C++17 compatibility, reversed operators are generated by the compiler +#define QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(LeftType, RightType, Constexpr) +#define QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(OrderingString, LeftType, RightType, Constexpr) + +#endif // Q_COMPILER_LACKS_THREE_WAY_COMPARE_SYMMETRY + +#else +// C++17 - provide operator==() and operator!=() for equality, +// and all 4 comparison operators for ordering + +#define QT_DECLARE_EQUALITY_OPERATORS_HELPER(LeftType, RightType, Constexpr) \ + friend Constexpr bool operator==(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(comparesEqual(lhs, rhs))) \ + { return comparesEqual(lhs, rhs); } \ + friend Constexpr bool operator!=(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(comparesEqual(lhs, rhs))) \ + { return !comparesEqual(lhs, rhs); } + +// Helpers for reversed comparison, using the existing comparesEqual() function. +#define QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(LeftType, RightType, Constexpr) \ + friend Constexpr bool operator==(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(comparesEqual(rhs, lhs))) \ + { return comparesEqual(rhs, lhs); } \ + friend Constexpr bool operator!=(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(comparesEqual(rhs, lhs))) \ + { return !comparesEqual(rhs, lhs); } + +#define QT_DECLARE_ORDERING_HELPER_TEMPLATE(OrderingType, LeftType, RightType, Constexpr) \ + friend Constexpr bool operator<(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(compareThreeWay(lhs, rhs))) \ + { return compareThreeWay(lhs, rhs) < 0; } \ + friend Constexpr bool operator>(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(compareThreeWay(lhs, rhs))) \ + { return compareThreeWay(lhs, rhs) > 0; } \ + friend Constexpr bool operator<=(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(compareThreeWay(lhs, rhs))) \ + { return compareThreeWay(lhs, rhs) <= 0; } \ + friend Constexpr bool operator>=(LeftType const &lhs, RightType const &rhs) \ + noexcept(noexcept(compareThreeWay(lhs, rhs))) \ + { return compareThreeWay(lhs, rhs) >= 0; } + +#define QT_DECLARE_ORDERING_HELPER_PARTIAL(LeftType, RightType, Constexpr) \ + QT_DECLARE_ORDERING_HELPER_TEMPLATE(Qt::partial_ordering, LeftType, RightType, Constexpr) + +#define QT_DECLARE_ORDERING_HELPER_WEAK(LeftType, RightType, Constexpr) \ + QT_DECLARE_ORDERING_HELPER_TEMPLATE(Qt::weak_ordering, LeftType, RightType, Constexpr) + +#define QT_DECLARE_ORDERING_HELPER_STRONG(LeftType, RightType, Constexpr) \ + QT_DECLARE_ORDERING_HELPER_TEMPLATE(Qt::strong_ordering, LeftType, RightType, Constexpr) + +#define QT_DECLARE_ORDERING_OPERATORS_HELPER(OrderingString, LeftType, RightType, Constexpr) \ + QT_DECLARE_EQUALITY_OPERATORS_HELPER(LeftType, RightType, Constexpr) \ + QT_DECLARE_ORDERING_HELPER_ ## OrderingString (LeftType, RightType, Constexpr) + +// Helpers for reversed ordering, using the existing compareThreeWay() function. +#define QT_DECLARE_REVERSED_ORDERING_HELPER_TEMPLATE(OrderingType, LeftType, RightType, Constexpr) \ + friend Constexpr bool operator<(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(compareThreeWay(rhs, lhs))) \ + { return compareThreeWay(rhs, lhs) > 0; } \ + friend Constexpr bool operator>(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(compareThreeWay(rhs, lhs))) \ + { return compareThreeWay(rhs, lhs) < 0; } \ + friend Constexpr bool operator<=(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(compareThreeWay(rhs, lhs))) \ + { return compareThreeWay(rhs, lhs) >= 0; } \ + friend Constexpr bool operator>=(RightType const &lhs, LeftType const &rhs) \ + noexcept(noexcept(compareThreeWay(rhs, lhs))) \ + { return compareThreeWay(rhs, lhs) <= 0; } + +#define QT_DECLARE_REVERSED_ORDERING_HELPER_PARTIAL(LeftType, RightType, Constexpr) \ + QT_DECLARE_REVERSED_ORDERING_HELPER_TEMPLATE(Qt::partial_ordering, LeftType, RightType, Constexpr) + +#define QT_DECLARE_REVERSED_ORDERING_HELPER_WEAK(LeftType, RightType, Constexpr) \ + QT_DECLARE_REVERSED_ORDERING_HELPER_TEMPLATE(Qt::weak_ordering, LeftType, RightType, Constexpr) + +#define QT_DECLARE_REVERSED_ORDERING_HELPER_STRONG(LeftType, RightType, Constexpr) \ + QT_DECLARE_REVERSED_ORDERING_HELPER_TEMPLATE(Qt::strong_ordering, LeftType, RightType, Constexpr) + +#define QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(OrderingString, LeftType, RightType, \ + Constexpr) \ + QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(LeftType, RightType, Constexpr) \ + QT_DECLARE_REVERSED_ORDERING_HELPER_ ## OrderingString (LeftType, RightType, Constexpr) + +#endif // __cpp_lib_three_way_comparison + +/* Public API starts here */ + +// Equality operators +#define QT_DECLARE_EQUALITY_COMPARABLE_1(Type) \ + QT_DECLARE_EQUALITY_OPERATORS_HELPER(Type, Type, /* non-constexpr */) + +#define QT_DECLARE_EQUALITY_COMPARABLE_2(LeftType, RightType) \ + QT_DECLARE_EQUALITY_OPERATORS_HELPER(LeftType, RightType, /* non-constexpr */) \ + QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(LeftType, RightType, /* non-constexpr */) + +#define Q_DECLARE_EQUALITY_COMPARABLE(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_EQUALITY_COMPARABLE, __VA_ARGS__) + +#define QT_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE_1(Type) \ + QT_DECLARE_EQUALITY_OPERATORS_HELPER(Type, Type, constexpr) + +#define QT_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE_2(LeftType, RightType) \ + QT_DECLARE_EQUALITY_OPERATORS_HELPER(LeftType, RightType, constexpr) \ + QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(LeftType, RightType, constexpr) + +#define Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE, __VA_ARGS__) + +// Partial ordering operators +#define QT_DECLARE_PARTIALLY_ORDERED_1(Type) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(PARTIAL, Type, Type, /* non-constexpr */) + +#define QT_DECLARE_PARTIALLY_ORDERED_2(LeftType, RightType) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(PARTIAL, LeftType, RightType, /* non-constexpr */) \ + QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(PARTIAL, LeftType, RightType, /* non-constexpr */) + +#define Q_DECLARE_PARTIALLY_ORDERED(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_PARTIALLY_ORDERED, __VA_ARGS__) + +#define QT_DECLARE_PARTIALLY_ORDERED_LITERAL_TYPE_1(Type) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(PARTIAL, Type, Type, constexpr) + +#define QT_DECLARE_PARTIALLY_ORDERED_LITERAL_TYPE_2(LeftType, RightType) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(PARTIAL, LeftType, RightType, constexpr) \ + QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(PARTIAL, LeftType, RightType, constexpr) + +#define Q_DECLARE_PARTIALLY_ORDERED_LITERAL_TYPE(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_PARTIALLY_ORDERED_LITERAL_TYPE, __VA_ARGS__) + +// Weak ordering operators +#define QT_DECLARE_WEAKLY_ORDERED_1(Type) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(WEAK, Type, Type, /* non-constexpr */) + +#define QT_DECLARE_WEAKLY_ORDERED_2(LeftType, RightType) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(WEAK, LeftType, RightType, /* non-constexpr */) \ + QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(WEAK, LeftType, RightType, /* non-constexpr */) + +#define Q_DECLARE_WEAKLY_ORDERED(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_WEAKLY_ORDERED, __VA_ARGS__) + +#define QT_DECLARE_WEAKLY_ORDERED_LITERAL_TYPE_1(Type) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(WEAK, Type, Type, constexpr) + +#define QT_DECLARE_WEAKLY_ORDERED_LITERAL_TYPE_2(LeftType, RightType) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(WEAK, LeftType, RightType, constexpr) \ + QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(WEAK, LeftType, RightType, constexpr) + +#define Q_DECLARE_WEAKLY_ORDERED_LITERAL_TYPE(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_WEAKLY_ORDERED_LITERAL_TYPE, __VA_ARGS__) + +// Strong ordering operators +#define QT_DECLARE_STRONGLY_ORDERED_1(Type) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(STRONG, Type, Type, /* non-constexpr */) + +#define QT_DECLARE_STRONGLY_ORDERED_2(LeftType, RightType) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(STRONG, LeftType, RightType, /* non-constexpr */) \ + QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(STRONG, LeftType, RightType, /* non-constexpr */) + +#define Q_DECLARE_STRONGLY_ORDERED(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_STRONGLY_ORDERED, __VA_ARGS__) + +#define QT_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE_1(Type) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(STRONG, Type, Type, constexpr) + +#define QT_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE_2(LeftType, RightType) \ + QT_DECLARE_ORDERING_OPERATORS_HELPER(STRONG, LeftType, RightType, constexpr) \ + QT_DECLARE_ORDERING_OPERATORS_REVERSED_HELPER(STRONG, LeftType, RightType, constexpr) + +#define Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE(...) \ + QT_OVERLOADED_MACRO(QT_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE, __VA_ARGS__) + +QT_END_NAMESPACE + +#endif // QCOMPAREHELPERS_H diff --git a/src/corelib/global/qcompilerdetection.h b/src/corelib/global/qcompilerdetection.h index 782599f16fb..4159eac0935 100644 --- a/src/corelib/global/qcompilerdetection.h +++ b/src/corelib/global/qcompilerdetection.h @@ -852,6 +852,13 @@ # if _MSC_VER >= 1910 # define Q_COMPILER_CONSTEXPR # endif +// MSVC versions before 19.36 have a bug in C++20 comparison implementation. +// This leads to ambiguities when resolving comparison operator overloads in +// certain scenarios (the buggy MSVC versions were checked using our CI and +// compiler explorer). +# if _MSC_VER < 1936 +# define Q_COMPILER_LACKS_THREE_WAY_COMPARE_SYMMETRY +# endif # endif /* __cplusplus */ #endif // defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) diff --git a/src/testlib/qcomparisontesthelper_p.h b/src/testlib/qcomparisontesthelper_p.h index 432b6f5bd37..00e00925dba 100644 --- a/src/testlib/qcomparisontesthelper_p.h +++ b/src/testlib/qcomparisontesthelper_p.h @@ -127,6 +127,7 @@ void testAllComparisonOperatorsCompile() Func(std::as_const(Left), std::as_const(Right), Op, Expected); \ /* END */ + /*! \internal Basic testing of equality operators. @@ -166,8 +167,9 @@ void testEqualityOperators(LeftType lhs, RightType rhs, bool expectedEqual) (==, !=, <, >, <=, >=) for the \a lhs operand of type \c {LeftType} and the \a rhs operand of type \c {RightType}. - The \c OrderingType must be one of Qt::partial_ordering, - Qt::weak_ordering, or Qt::strong_ordering. + When compiled in C++17 mode, the \c OrderingType must be one of + Qt::partial_ordering, Qt::strong_ordering, or Qt::weak_ordering. + In C++20 mode, also the \c {std::*_ordering} types can be used. The \a expectedOrdering parameter provides the expected relation between \a lhs and \a rhs. @@ -189,9 +191,18 @@ void testAllComparisonOperators(LeftType lhs, RightType rhs, OrderingType expect constexpr bool isQOrderingType = std::is_same_v || std::is_same_v || std::is_same_v; - static_assert(isQOrderingType, +#ifdef __cpp_lib_three_way_comparison + constexpr bool isStdOrderingType = std::is_same_v + || std::is_same_v + || std::is_same_v; +#else + constexpr bool isStdOrderingType = false; +#endif + + static_assert(isQOrderingType || isStdOrderingType, "Please provide, as the expectedOrdering parameter, a value " - "of one of the Qt::{partial,weak,strong_ordering types."); + "of one of the Qt::{partial,weak,strong}_ordering or " + "std::{partial,weak,strong}_ordering types."); // We have all sorts of operator==() between Q*Ordering and std::*_ordering // types, so we can just compare to Qt::partial_ordering. diff --git a/tests/auto/corelib/global/CMakeLists.txt b/tests/auto/corelib/global/CMakeLists.txt index 2204f82ba3c..7970116672d 100644 --- a/tests/auto/corelib/global/CMakeLists.txt +++ b/tests/auto/corelib/global/CMakeLists.txt @@ -4,6 +4,7 @@ if(NOT INTEGRITY) add_subdirectory(qcompare) endif() +add_subdirectory(qcomparehelpers) add_subdirectory(qflags) add_subdirectory(q_func_info) add_subdirectory(qgetputenv) diff --git a/tests/auto/corelib/global/qcomparehelpers/CMakeLists.txt b/tests/auto/corelib/global/qcomparehelpers/CMakeLists.txt new file mode 100644 index 00000000000..ab0620e8b13 --- /dev/null +++ b/tests/auto/corelib/global/qcomparehelpers/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qcomparehelpers + SOURCES + tst_qcomparehelpers.cpp + LIBRARIES + Qt::TestPrivate +) diff --git a/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.cpp b/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.cpp new file mode 100644 index 00000000000..e3810f86052 --- /dev/null +++ b/tests/auto/corelib/global/qcomparehelpers/tst_qcomparehelpers.cpp @@ -0,0 +1,491 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include +#include + +class IntWrapper +{ +public: + // implicit constructor and operator int() to simulate the case that + // triggers a bug on MSVC < 19.36. + IntWrapper(int val) : m_val(val) {} + operator int() const noexcept { return m_val; } + + int value() const { return m_val; } + +private: + friend bool comparesEqual(const IntWrapper &lhs, const IntWrapper &rhs) noexcept + { return lhs.m_val == rhs.m_val; } + friend Qt::strong_ordering + compareThreeWay(const IntWrapper &lhs, const IntWrapper &rhs) noexcept + { + // ### Qt::compareThreeWay + if (lhs.m_val < rhs.m_val) + return Qt::strong_ordering::less; + else if (lhs.m_val > rhs.m_val) + return Qt::strong_ordering::greater; + else + return Qt::strong_ordering::equal; + } + friend bool comparesEqual(const IntWrapper &lhs, int rhs) noexcept + { return lhs.m_val == rhs; } + friend Qt::strong_ordering compareThreeWay(const IntWrapper &lhs, int rhs) noexcept + { return compareThreeWay(lhs, IntWrapper(rhs)); } + + Q_DECLARE_STRONGLY_ORDERED(IntWrapper) + Q_DECLARE_STRONGLY_ORDERED(IntWrapper, int) + + int m_val = 0; +}; + +class DoubleWrapper +{ +public: + explicit DoubleWrapper(double val) : m_val(val) {} + double value() const { return m_val; } + +private: + friend bool comparesEqual(const DoubleWrapper &lhs, const DoubleWrapper &rhs) noexcept + { return lhs.m_val == rhs.m_val; } + friend Qt::partial_ordering + compareThreeWay(const DoubleWrapper &lhs, const DoubleWrapper &rhs) noexcept + { + // ### Qt::compareThreeWay + if (qIsNaN(lhs.m_val) || qIsNaN(rhs.m_val)) + return Qt::partial_ordering::unordered; + + if (lhs.m_val < rhs.m_val) + return Qt::partial_ordering::less; + else if (lhs.m_val > rhs.m_val) + return Qt::partial_ordering::greater; + else + return Qt::partial_ordering::equivalent; + } + friend bool comparesEqual(const DoubleWrapper &lhs, const IntWrapper &rhs) noexcept + { return comparesEqual(lhs, DoubleWrapper(rhs.value())); } + friend Qt::partial_ordering + compareThreeWay(const DoubleWrapper &lhs, const IntWrapper &rhs) noexcept + { return compareThreeWay(lhs, DoubleWrapper(rhs.value())); } + friend bool comparesEqual(const DoubleWrapper &lhs, double rhs) noexcept + { return lhs.m_val == rhs; } + friend Qt::partial_ordering compareThreeWay(const DoubleWrapper &lhs, double rhs) noexcept + { + // ### Qt::compareThreeWay + if (qIsNaN(lhs.m_val) || qIsNaN(rhs)) + return Qt::partial_ordering::unordered; + + if (lhs.m_val < rhs) + return Qt::partial_ordering::less; + else if (lhs.m_val > rhs) + return Qt::partial_ordering::greater; + else + return Qt::partial_ordering::equivalent; + } + + Q_DECLARE_PARTIALLY_ORDERED(DoubleWrapper) + Q_DECLARE_PARTIALLY_ORDERED(DoubleWrapper, IntWrapper) + Q_DECLARE_PARTIALLY_ORDERED(DoubleWrapper, double) + + double m_val = 0.0; +}; + +template +class StringWrapper +{ +public: + explicit StringWrapper(String val) : m_val(val) {} + String value() const { return m_val; } + +private: + // Some of the helper functions are intentionally NOT marked as noexcept + // to test the conditional noexcept in the macros. + template + friend bool comparesEqual(const StringWrapper &, const StringWrapper &) noexcept; + template + friend Qt::weak_ordering + compareThreeWay(const StringWrapper &, const StringWrapper &) noexcept; + template + friend bool comparesEqual(const StringWrapper &, QAnyStringView); + template + friend Qt::weak_ordering compareThreeWay(const StringWrapper &, QAnyStringView); + + Q_DECLARE_WEAKLY_ORDERED(StringWrapper) + Q_DECLARE_WEAKLY_ORDERED(StringWrapper, QAnyStringView) + + String m_val; +}; + +// StringWrapper comparison helper functions + +bool equalsHelper(QAnyStringView lhs, QAnyStringView rhs) noexcept +{ + return QAnyStringView::compare(lhs, rhs, Qt::CaseInsensitive) == 0; +} + +template +bool comparesEqual(const StringWrapper &lhs, const StringWrapper &rhs) noexcept +{ + return equalsHelper(lhs.m_val, rhs.m_val); +} + +Qt::weak_ordering compareHelper(QAnyStringView lhs, QAnyStringView rhs) noexcept +{ + const int res = QAnyStringView::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; +} + +template +Qt::weak_ordering compareThreeWay(const StringWrapper &lhs, const StringWrapper &rhs) noexcept +{ + return compareHelper(lhs.m_val, rhs.m_val); +} + +template +bool comparesEqual(const StringWrapper &lhs, QAnyStringView rhs) +{ + return equalsHelper(lhs.m_val, rhs); +} + +template +Qt::weak_ordering compareThreeWay(const StringWrapper &lhs, QAnyStringView rhs) +{ + return compareHelper(lhs.m_val, rhs); +} + +// Test class + +class tst_QCompareHelpers : public QObject +{ + Q_OBJECT + +private: + template + void compareImpl(); + + template + void compareIntData(); + + template + void compareFloatData(); + + template + void compareStringData(); + +private slots: + void comparisonCompiles(); + + void compare_IntWrapper_data() { compareIntData(); } + void compare_IntWrapper() { compareImpl(); } + + void compare_IntWrapper_int_data() { compareIntData(); } + void compare_IntWrapper_int() { compareImpl(); } + + void compare_DoubleWrapper_data() { compareFloatData(); } + void compare_DoubleWrapper() + { compareImpl(); } + + void compare_DoubleWrapper_double_data() { compareFloatData(); } + void compare_DoubleWrapper_double() + { compareImpl(); } + + void compare_IntWrapper_DoubleWrapper_data(); + void compare_IntWrapper_DoubleWrapper() + { compareImpl(); } + + void compare_StringWrapper_data() + { compareStringData, StringWrapper>(); } + void compare_StringWrapper() + { compareImpl, StringWrapper, Qt::weak_ordering>(); } + + void compare_StringWrapper_AnyStringView_data() + { compareStringData, QAnyStringView>(); } + void compare_StringWrapper_AnyStringView() + { compareImpl, QAnyStringView, Qt::weak_ordering>(); } + + void generatedClasses(); +}; + +template +void tst_QCompareHelpers::compareImpl() +{ + QFETCH(LeftType, lhs); + QFETCH(RightType, rhs); + QFETCH(OrderingType, expectedOrdering); + + QTestPrivate::testAllComparisonOperators(lhs, rhs, expectedOrdering); + if (QTest::currentTestFailed()) + return; +#ifdef __cpp_lib_three_way_comparison + // Also check std types. + + // if Ordering == Qt::strong_ordering -> std::strong_ordering + // else if Ordering == Qt::weak_ordering -> std::weak_ordering + // else std::partial_ordering + using StdType = std::conditional_t< + std::is_same_v, + std::strong_ordering, + std::conditional_t, + std::weak_ordering, + std::partial_ordering>>; + + QTestPrivate::testAllComparisonOperators(lhs, rhs, static_cast(expectedOrdering)); + if (QTest::currentTestFailed()) + return; +#endif // __cpp_lib_three_way_comparison +} + +template +void tst_QCompareHelpers::compareIntData() +{ + QTest::addColumn("lhs"); + QTest::addColumn("rhs"); + QTest::addColumn("expectedOrdering"); + + auto createRow = [](auto lhs, auto rhs, Qt::strong_ordering ordering) { + QTest::addRow("%d vs %d", lhs, rhs) << LeftType(lhs) << RightType(rhs) << ordering; + }; + + createRow(0, 0, Qt::strong_ordering::equivalent); + createRow(-1, 0, Qt::strong_ordering::less); + createRow(1, 0, Qt::strong_ordering::greater); + constexpr int max = std::numeric_limits::max(); + constexpr int min = std::numeric_limits::min(); + createRow(max, max, Qt::strong_ordering::equivalent); + createRow(min, min, Qt::strong_ordering::equivalent); + createRow(max, min, Qt::strong_ordering::greater); + createRow(min, max, Qt::strong_ordering::less); +} + +template +void tst_QCompareHelpers::compareFloatData() +{ + QTest::addColumn("lhs"); + QTest::addColumn("rhs"); + QTest::addColumn("expectedOrdering"); + + auto createRow = [](auto lhs, auto rhs, Qt::partial_ordering ordering) { + QTest::addRow("%f vs %f", lhs, rhs) << LeftType(lhs) << RightType(rhs) << ordering; + }; + + createRow(0.0, 0.0, Qt::partial_ordering::equivalent); + createRow(-0.000001, 0.0, Qt::partial_ordering::less); + createRow(0.000001, 0.0, Qt::partial_ordering::greater); + + const double nan = qQNaN(); + createRow(nan, 0.0, Qt::partial_ordering::unordered); + createRow(0.0, nan, Qt::partial_ordering::unordered); + createRow(nan, nan, Qt::partial_ordering::unordered); + + const double inf = qInf(); + createRow(inf, 0.0, Qt::partial_ordering::greater); + createRow(0.0, inf, Qt::partial_ordering::less); + createRow(-inf, 0.0, Qt::partial_ordering::less); + createRow(0.0, -inf, Qt::partial_ordering::greater); + createRow(inf, inf, Qt::partial_ordering::equivalent); + createRow(-inf, -inf, Qt::partial_ordering::equivalent); + createRow(-inf, inf, Qt::partial_ordering::less); + createRow(inf, -inf, Qt::partial_ordering::greater); + + createRow(nan, inf, Qt::partial_ordering::unordered); + createRow(inf, nan, Qt::partial_ordering::unordered); + createRow(nan, -inf, Qt::partial_ordering::unordered); + createRow(-inf, nan, Qt::partial_ordering::unordered); +} + +template +void tst_QCompareHelpers::compareStringData() +{ + QTest::addColumn("lhs"); + QTest::addColumn("rhs"); + QTest::addColumn("expectedOrdering"); + + auto createRow = [](auto lhs, auto rhs, Qt::weak_ordering ordering) { + QTest::addRow("'%s' vs '%s'", lhs, rhs) << LeftType(lhs) << RightType(rhs) << ordering; + }; + + createRow("", "", Qt::weak_ordering::equivalent); + createRow("Ab", "abc", Qt::weak_ordering::less); + createRow("aBc", "AB", Qt::weak_ordering::greater); + createRow("ab", "AB", Qt::weak_ordering::equivalent); + createRow("ABC", "abc", Qt::weak_ordering::equivalent); +} + +void tst_QCompareHelpers::comparisonCompiles() +{ + QTestPrivate::testAllComparisonOperatorsCompile(); + if (QTest::currentTestFailed()) + return; + + QTestPrivate::testAllComparisonOperatorsCompile(); + if (QTest::currentTestFailed()) + return; + + QTestPrivate::testAllComparisonOperatorsCompile(); + if (QTest::currentTestFailed()) + return; + + QTestPrivate::testAllComparisonOperatorsCompile(); + if (QTest::currentTestFailed()) + return; + + QTestPrivate::testAllComparisonOperatorsCompile(); + if (QTest::currentTestFailed()) + return; + + QTestPrivate::testAllComparisonOperatorsCompile>(); + if (QTest::currentTestFailed()) + return; + + QTestPrivate::testAllComparisonOperatorsCompile, QAnyStringView>(); + if (QTest::currentTestFailed()) + return; +} + +void tst_QCompareHelpers::compare_IntWrapper_DoubleWrapper_data() +{ + QTest::addColumn("lhs"); + QTest::addColumn("rhs"); + QTest::addColumn("expectedOrdering"); + + auto createRow = [](auto lhs, auto rhs, Qt::partial_ordering ordering) { + QTest::addRow("%d vs %f", lhs, rhs) << IntWrapper(lhs) << DoubleWrapper(rhs) << ordering; + }; + + createRow(0, 0.0, Qt::partial_ordering::equivalent); + createRow(-1, 0.0, Qt::partial_ordering::less); + createRow(1, 0.0, Qt::partial_ordering::greater); + createRow(0, -0.000001, Qt::partial_ordering::greater); + createRow(0, 0.000001, Qt::partial_ordering::less); + + constexpr int max = std::numeric_limits::max(); + constexpr int min = std::numeric_limits::min(); + const double nan = qQNaN(); + createRow(0, nan, Qt::partial_ordering::unordered); + createRow(max, nan, Qt::partial_ordering::unordered); + createRow(min, nan, Qt::partial_ordering::unordered); + + const double inf = qInf(); + createRow(0, inf, Qt::partial_ordering::less); + createRow(0, -inf, Qt::partial_ordering::greater); + createRow(max, inf, Qt::partial_ordering::less); + createRow(max, -inf, Qt::partial_ordering::greater); + createRow(min, inf, Qt::partial_ordering::less); + createRow(min, -inf, Qt::partial_ordering::greater); +} + +#define DECLARE_TYPE(Name, Type, Attrs, RetType, Constexpr, Suffix) \ +class Dummy ## Name \ +{ \ +public: \ + Constexpr Dummy ## Name () {} \ +\ +private: \ + friend Attrs Constexpr bool \ + comparesEqual(const Dummy ## Name &lhs, const Dummy ## Name &rhs) noexcept; \ + friend Attrs Constexpr RetType \ + compareThreeWay(const Dummy ## Name &lhs, const Dummy ## Name &rhs) noexcept; \ + friend Attrs Constexpr bool \ + comparesEqual(const Dummy ## Name &lhs, int rhs) noexcept; \ + friend Attrs Constexpr RetType \ + compareThreeWay(const Dummy ## Name &lhs, int rhs) noexcept; \ + Q_DECLARE_ ## Type ##_ORDERED ## Suffix (Dummy ## Name) \ + Q_DECLARE_ ## Type ##_ORDERED ## Suffix (Dummy ## Name, int) \ +}; \ +\ +Attrs Constexpr bool comparesEqual(const Dummy ## Name &lhs, const Dummy ## Name &rhs) noexcept \ +{ Q_UNUSED(lhs); Q_UNUSED(rhs); return true; } \ +Attrs Constexpr RetType \ +compareThreeWay(const Dummy ## Name &lhs, const Dummy ## Name &rhs) noexcept \ +{ Q_UNUSED(lhs); Q_UNUSED(rhs); return RetType::equivalent; } \ +Attrs Constexpr bool comparesEqual(const Dummy ## Name &lhs, int rhs) noexcept \ +{ Q_UNUSED(lhs); Q_UNUSED(rhs); return true; } \ +Attrs Constexpr RetType compareThreeWay(const Dummy ## Name &lhs, int rhs) noexcept \ +{ Q_UNUSED(lhs); Q_UNUSED(rhs); return RetType::equivalent; } + +DECLARE_TYPE(PartialConstAttr, PARTIALLY, Q_DECL_PURE_FUNCTION, Qt::partial_ordering, constexpr, + _LITERAL_TYPE) +DECLARE_TYPE(PartialConst, PARTIALLY, /* no attrs */, Qt::partial_ordering, constexpr, _LITERAL_TYPE) +DECLARE_TYPE(PartialAttr, PARTIALLY, Q_DECL_CONST_FUNCTION, Qt::partial_ordering, , ) +DECLARE_TYPE(Partial, PARTIALLY, /* no attrs */, Qt::partial_ordering, , ) + +DECLARE_TYPE(WeakConstAttr, WEAKLY, Q_DECL_PURE_FUNCTION, Qt::weak_ordering, constexpr, _LITERAL_TYPE) +DECLARE_TYPE(WeakConst, WEAKLY, /* no attrs */, Qt::weak_ordering, constexpr, _LITERAL_TYPE) +DECLARE_TYPE(WeakAttr, WEAKLY, Q_DECL_CONST_FUNCTION, Qt::weak_ordering, , ) +DECLARE_TYPE(Weak, WEAKLY, /* no attrs */, Qt::weak_ordering, , ) + +DECLARE_TYPE(StrongConstAttr, STRONGLY, Q_DECL_PURE_FUNCTION, Qt::strong_ordering, constexpr, + _LITERAL_TYPE) +DECLARE_TYPE(StrongConst, STRONGLY, /* no attrs */, Qt::strong_ordering, constexpr, _LITERAL_TYPE) +DECLARE_TYPE(StrongAttr, STRONGLY, Q_DECL_CONST_FUNCTION, Qt::strong_ordering, , ) +DECLARE_TYPE(Strong, STRONGLY, /* no attrs */, Qt::strong_ordering, , ) + +#define DECLARE_EQUALITY_COMPARABLE(Name, Attrs, Constexpr, Suffix) \ +class Dummy ## Name \ +{ \ +public: \ + Constexpr Dummy ## Name (int) {} \ +\ +private: \ + friend Attrs Constexpr bool \ + comparesEqual(const Dummy ## Name &lhs, const Dummy ## Name &rhs) noexcept; \ + friend Attrs Constexpr bool comparesEqual(const Dummy ## Name &lhs, int rhs) noexcept; \ + Q_DECLARE_EQUALITY_COMPARABLE ## Suffix (Dummy ## Name) \ + Q_DECLARE_EQUALITY_COMPARABLE ## Suffix (Dummy ## Name, int) \ +}; \ +\ +Attrs Constexpr bool comparesEqual(const Dummy ## Name &lhs, const Dummy ## Name &rhs) noexcept \ +{ Q_UNUSED(lhs); Q_UNUSED(rhs); return true; } \ +Attrs Constexpr bool comparesEqual(const Dummy ## Name &lhs, int rhs) noexcept \ +{ Q_UNUSED(lhs); Q_UNUSED(rhs); return true; } \ + +DECLARE_EQUALITY_COMPARABLE(ConstAttr, Q_DECL_PURE_FUNCTION, constexpr, _LITERAL_TYPE) +DECLARE_EQUALITY_COMPARABLE(Const, /* no attrs */, constexpr, _LITERAL_TYPE) +DECLARE_EQUALITY_COMPARABLE(Attr, Q_DECL_CONST_FUNCTION, , ) +DECLARE_EQUALITY_COMPARABLE(None, /* no attrs */, , ) + +void tst_QCompareHelpers::generatedClasses() +{ +#define COMPARE(ClassName) \ + do { \ + QTestPrivate::testAllComparisonOperatorsCompile(); \ + QTestPrivate::testAllComparisonOperatorsCompile(); \ + } while (0) + + COMPARE(DummyPartialConstAttr); + COMPARE(DummyPartialConst); + COMPARE(DummyPartialAttr); + COMPARE(DummyPartial); + + COMPARE(DummyWeakConstAttr); + COMPARE(DummyWeakConst); + COMPARE(DummyWeakAttr); + COMPARE(DummyWeak); + + COMPARE(DummyStrongConstAttr); + COMPARE(DummyStrongConst); + COMPARE(DummyStrongAttr); + COMPARE(DummyStrong); +#undef COMPARE + + QTestPrivate::testEqualityOperatorsCompile(); + QTestPrivate::testEqualityOperatorsCompile(); + + QTestPrivate::testEqualityOperatorsCompile(); + QTestPrivate::testEqualityOperatorsCompile(); + + QTestPrivate::testEqualityOperatorsCompile(); + QTestPrivate::testEqualityOperatorsCompile(); + + QTestPrivate::testEqualityOperatorsCompile(); + QTestPrivate::testEqualityOperatorsCompile(); +} + +QTEST_MAIN(tst_QCompareHelpers) +#include "tst_qcomparehelpers.moc"