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 <edward.welbourne@qt.io> Reviewed-by: Marc Mutz <marc.mutz@qt.io>
This commit is contained in:
parent
e616f8decb
commit
fe12650e9d
@ -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
|
||||
|
@ -122,6 +122,20 @@ CHECK(strong, equivalent);
|
||||
implementing three-way comparison with C++17.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\headerfile <QtCompare>
|
||||
\inmodule QtCore
|
||||
\title Classes and helpers for defining comparison operators
|
||||
\keyword qtcompare
|
||||
|
||||
\brief The <QtCompare> 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 <QtCompare>
|
||||
|
||||
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 <QtCompare>
|
||||
|
||||
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 <QtCompare>
|
||||
|
||||
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 <QtCompare>
|
||||
|
||||
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
|
||||
|
@ -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 <QtCore/qcomparehelpers.h>
|
||||
|
||||
#endif // QCOMPARE_H
|
||||
|
307
src/corelib/global/qcomparehelpers.h
Normal file
307
src/corelib/global/qcomparehelpers.h
Normal file
@ -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 <QtCore/qoverload.h>
|
||||
|
||||
#ifdef __cpp_lib_three_way_comparison
|
||||
#include <compare>
|
||||
#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_<Module>_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
|
@ -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)
|
||||
|
||||
|
@ -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<OrderingType, Qt::partial_ordering>
|
||||
|| std::is_same_v<OrderingType, Qt::weak_ordering>
|
||||
|| std::is_same_v<OrderingType, Qt::strong_ordering>;
|
||||
static_assert(isQOrderingType,
|
||||
#ifdef __cpp_lib_three_way_comparison
|
||||
constexpr bool isStdOrderingType = std::is_same_v<OrderingType, std::partial_ordering>
|
||||
|| std::is_same_v<OrderingType, std::weak_ordering>
|
||||
|| std::is_same_v<OrderingType, std::strong_ordering>;
|
||||
#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.
|
||||
|
@ -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)
|
||||
|
9
tests/auto/corelib/global/qcomparehelpers/CMakeLists.txt
Normal file
9
tests/auto/corelib/global/qcomparehelpers/CMakeLists.txt
Normal file
@ -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
|
||||
)
|
@ -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 <QtCore/qcompare.h>
|
||||
#include <QtTest/qtest.h>
|
||||
#include <QtTest/private/qcomparisontesthelper_p.h>
|
||||
|
||||
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 <typename String>
|
||||
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 <typename T>
|
||||
friend bool comparesEqual(const StringWrapper<T> &, const StringWrapper<T> &) noexcept;
|
||||
template <typename T>
|
||||
friend Qt::weak_ordering
|
||||
compareThreeWay(const StringWrapper<T> &, const StringWrapper<T> &) noexcept;
|
||||
template <typename T>
|
||||
friend bool comparesEqual(const StringWrapper<T> &, QAnyStringView);
|
||||
template <typename T>
|
||||
friend Qt::weak_ordering compareThreeWay(const StringWrapper<T> &, 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 <typename T>
|
||||
bool comparesEqual(const StringWrapper<T> &lhs, const StringWrapper<T> &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 <typename T>
|
||||
Qt::weak_ordering compareThreeWay(const StringWrapper<T> &lhs, const StringWrapper<T> &rhs) noexcept
|
||||
{
|
||||
return compareHelper(lhs.m_val, rhs.m_val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool comparesEqual(const StringWrapper<T> &lhs, QAnyStringView rhs)
|
||||
{
|
||||
return equalsHelper(lhs.m_val, rhs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Qt::weak_ordering compareThreeWay(const StringWrapper<T> &lhs, QAnyStringView rhs)
|
||||
{
|
||||
return compareHelper(lhs.m_val, rhs);
|
||||
}
|
||||
|
||||
// Test class
|
||||
|
||||
class tst_QCompareHelpers : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
template <typename LeftType, typename RightType, typename OrderingType>
|
||||
void compareImpl();
|
||||
|
||||
template <typename LeftType, typename RightType>
|
||||
void compareIntData();
|
||||
|
||||
template <typename LeftType, typename RightType>
|
||||
void compareFloatData();
|
||||
|
||||
template <typename LeftType, typename RightType>
|
||||
void compareStringData();
|
||||
|
||||
private slots:
|
||||
void comparisonCompiles();
|
||||
|
||||
void compare_IntWrapper_data() { compareIntData<IntWrapper, IntWrapper>(); }
|
||||
void compare_IntWrapper() { compareImpl<IntWrapper, IntWrapper, Qt::strong_ordering>(); }
|
||||
|
||||
void compare_IntWrapper_int_data() { compareIntData<IntWrapper, int>(); }
|
||||
void compare_IntWrapper_int() { compareImpl<IntWrapper, int, Qt::strong_ordering>(); }
|
||||
|
||||
void compare_DoubleWrapper_data() { compareFloatData<DoubleWrapper, DoubleWrapper>(); }
|
||||
void compare_DoubleWrapper()
|
||||
{ compareImpl<DoubleWrapper, DoubleWrapper, Qt::partial_ordering>(); }
|
||||
|
||||
void compare_DoubleWrapper_double_data() { compareFloatData<DoubleWrapper, double>(); }
|
||||
void compare_DoubleWrapper_double()
|
||||
{ compareImpl<DoubleWrapper, double, Qt::partial_ordering>(); }
|
||||
|
||||
void compare_IntWrapper_DoubleWrapper_data();
|
||||
void compare_IntWrapper_DoubleWrapper()
|
||||
{ compareImpl<IntWrapper, DoubleWrapper, Qt::partial_ordering>(); }
|
||||
|
||||
void compare_StringWrapper_data()
|
||||
{ compareStringData<StringWrapper<QString>, StringWrapper<QString>>(); }
|
||||
void compare_StringWrapper()
|
||||
{ compareImpl<StringWrapper<QString>, StringWrapper<QString>, Qt::weak_ordering>(); }
|
||||
|
||||
void compare_StringWrapper_AnyStringView_data()
|
||||
{ compareStringData<StringWrapper<QString>, QAnyStringView>(); }
|
||||
void compare_StringWrapper_AnyStringView()
|
||||
{ compareImpl<StringWrapper<QString>, QAnyStringView, Qt::weak_ordering>(); }
|
||||
|
||||
void generatedClasses();
|
||||
};
|
||||
|
||||
template<typename LeftType, typename RightType, typename OrderingType>
|
||||
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<OrderingType, Qt::strong_ordering>,
|
||||
std::strong_ordering,
|
||||
std::conditional_t<std::is_same_v<OrderingType, Qt::weak_ordering>,
|
||||
std::weak_ordering,
|
||||
std::partial_ordering>>;
|
||||
|
||||
QTestPrivate::testAllComparisonOperators(lhs, rhs, static_cast<StdType>(expectedOrdering));
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
#endif // __cpp_lib_three_way_comparison
|
||||
}
|
||||
|
||||
template<typename LeftType, typename RightType>
|
||||
void tst_QCompareHelpers::compareIntData()
|
||||
{
|
||||
QTest::addColumn<LeftType>("lhs");
|
||||
QTest::addColumn<RightType>("rhs");
|
||||
QTest::addColumn<Qt::strong_ordering>("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<int>::max();
|
||||
constexpr int min = std::numeric_limits<int>::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<typename LeftType, typename RightType>
|
||||
void tst_QCompareHelpers::compareFloatData()
|
||||
{
|
||||
QTest::addColumn<LeftType>("lhs");
|
||||
QTest::addColumn<RightType>("rhs");
|
||||
QTest::addColumn<Qt::partial_ordering>("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<typename LeftType, typename RightType>
|
||||
void tst_QCompareHelpers::compareStringData()
|
||||
{
|
||||
QTest::addColumn<LeftType>("lhs");
|
||||
QTest::addColumn<RightType>("rhs");
|
||||
QTest::addColumn<Qt::weak_ordering>("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<IntWrapper>();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
QTestPrivate::testAllComparisonOperatorsCompile<IntWrapper, int>();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
QTestPrivate::testAllComparisonOperatorsCompile<DoubleWrapper>();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
QTestPrivate::testAllComparisonOperatorsCompile<DoubleWrapper, double>();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
QTestPrivate::testAllComparisonOperatorsCompile<DoubleWrapper, IntWrapper>();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
QTestPrivate::testAllComparisonOperatorsCompile<StringWrapper<QString>>();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
|
||||
QTestPrivate::testAllComparisonOperatorsCompile<StringWrapper<QString>, QAnyStringView>();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
}
|
||||
|
||||
void tst_QCompareHelpers::compare_IntWrapper_DoubleWrapper_data()
|
||||
{
|
||||
QTest::addColumn<IntWrapper>("lhs");
|
||||
QTest::addColumn<DoubleWrapper>("rhs");
|
||||
QTest::addColumn<Qt::partial_ordering>("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<int>::max();
|
||||
constexpr int min = std::numeric_limits<int>::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<ClassName>(); \
|
||||
QTestPrivate::testAllComparisonOperatorsCompile<ClassName, int>(); \
|
||||
} 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<DummyConstAttr>();
|
||||
QTestPrivate::testEqualityOperatorsCompile<DummyConstAttr, int>();
|
||||
|
||||
QTestPrivate::testEqualityOperatorsCompile<DummyConst>();
|
||||
QTestPrivate::testEqualityOperatorsCompile<DummyConst, int>();
|
||||
|
||||
QTestPrivate::testEqualityOperatorsCompile<DummyAttr>();
|
||||
QTestPrivate::testEqualityOperatorsCompile<DummyAttr, int>();
|
||||
|
||||
QTestPrivate::testEqualityOperatorsCompile<DummyNone>();
|
||||
QTestPrivate::testEqualityOperatorsCompile<DummyNone, int>();
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QCompareHelpers)
|
||||
#include "tst_qcomparehelpers.moc"
|
Loading…
x
Reference in New Issue
Block a user