From 2e2c5d01999f2a9807f756fdf84b4a39fc492cc9 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Thu, 17 Sep 2026 12:38:18 +0200 Subject: [PATCH] Implement spaceship operator <=> for No-STL case (#1604) This was disabled explicitly in recent commit 232c948674cff1b4b72c2d6b3acfa7342d096dd1 to prevent using the header from STL. Now, re-enabling by implementing an ETL version of it in compare.h --- docs/utilities/compare.md | 103 ++++++- include/etl/chrono.h | 1 + include/etl/compare.h | 295 +++++++++++++++++++- include/etl/private/chrono/day.h | 4 +- include/etl/private/chrono/duration.h | 32 ++- include/etl/private/chrono/month.h | 4 +- include/etl/private/chrono/month_day.h | 6 +- include/etl/private/chrono/time_point.h | 2 +- include/etl/private/chrono/year.h | 4 +- include/etl/private/chrono/year_month.h | 2 +- include/etl/private/chrono/year_month_day.h | 4 +- test/test_chrono_day.cpp | 2 +- test/test_chrono_duration.cpp | 33 ++- test/test_chrono_month.cpp | 2 +- test/test_chrono_month_day.cpp | 8 +- test/test_chrono_month_day_last.cpp | 14 + test/test_chrono_time_point.cpp | 4 +- test/test_chrono_year.cpp | 2 +- test/test_chrono_year_month.cpp | 12 +- test/test_chrono_year_month_day.cpp | 16 +- test/test_chrono_year_month_day_last.cpp | 17 ++ test/test_compare.cpp | 148 ++++++++++ 22 files changed, 667 insertions(+), 48 deletions(-) diff --git a/docs/utilities/compare.md b/docs/utilities/compare.md index f6062c73..54d904e5 100644 --- a/docs/utilities/compare.md +++ b/docs/utilities/compare.md @@ -7,23 +7,32 @@ title: "compare" Since: `TBC` {{< /callout >}} -A helper class that defines `<=`, `>`, `>=` in terms of `etl::less` or a user supplied compare type. +A helper for deriving comparison operations from a strict weak ordering. +`etl::compare` exposes boolean predicates and a three-state comparison result. +`etl::three_way_compare` returns `-1`, `0`, or `1` using the same comparison policy. ```cpp template > struct compare ``` +```cpp +template > +ETL_CONSTEXPR14 int three_way_compare(const T& lhs, const T& rhs) +``` + ## Types ```cpp first_argument_type; second_argument_type; -result_type; +cmp_result; ``` +`cmp_result` has the values `Less`, `Equal`, and `Greater`. + ## Members ```cpp -static result_type lt(first_argument_type lhs, second_argument_type rhs) +static bool lt(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs < rhs`, otherwise `false`. @@ -31,7 +40,7 @@ static result_type lt(first_argument_type lhs, second_argument_type rhs) --- ```cpp -static result_type lte(first_argument_type lhs, second_argument_type rhs) +static bool lte(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs <= rhs`, otherwise `false`. @@ -39,7 +48,7 @@ static result_type lte(first_argument_type lhs, second_argument_type rhs) --- ```cpp -static result_type gt(first_argument_type lhs, second_argument_type rhs) +static bool gt(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs > rhs`, otherwise `false`. @@ -47,14 +56,74 @@ static result_type gt(first_argument_type lhs, second_argument_type rhs) --- ```cpp -static result_type gte(first_argument_type lhs, second_argument_type rhs) +static bool gte(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs >= rhs`, otherwise `false`. +--- + +```cpp +static bool eq(first_argument_type lhs, second_argument_type rhs) +``` +**Return** +`true` if `lhs == rhs`, otherwise `false`. + +--- + +```cpp +static bool ne(first_argument_type lhs, second_argument_type rhs) +``` +**Return** +`true` if `lhs != rhs`, otherwise `false`. + +--- + +```cpp +static cmp_result cmp(first_argument_type lhs, second_argument_type rhs) +``` +**Return** +`Less`, `Equal`, or `Greater`. + +## Three-way comparison + +`etl::three_way_compare(lhs, rhs)` returns the same ordering as `compare::cmp(lhs, rhs)`, +but as an `int` with the values `-1`, `0`, or `1`. + +## C++20 comparison categories + +When compiling as C++20, `compare.h` makes the comparison categories available in the `etl` +namespace, so ETL and user code can name them regardless of the STL configuration. + +```cpp +class strong_ordering; +class weak_ordering; +class partial_ordering; +``` + +* When the STL is available these are aliases for `std::strong_ordering`, `std::weak_ordering` + and `std::partial_ordering`. +* When the STL is not available, `compare.h` provides its own minimal implementations that + follow the standard semantics: the named values `less`, `equivalent`, `greater` + (plus `equal` for `strong_ordering` and `unordered` for `partial_ordering`), comparison + against the literal `0` only, and the implicit conversions + `strong_ordering` → `weak_ordering` → `partial_ordering`. + +An `unordered` result compares `false` against `0` for all of `<`, `<=`, `==`, `>=` and `>`. + +```cpp +ETL_CONSTEXPR strong_ordering make_strong_ordering(int value) ETL_NOEXCEPT; +ETL_CONSTEXPR weak_ordering make_weak_ordering(int value) ETL_NOEXCEPT; +ETL_CONSTEXPR partial_ordering make_partial_ordering(int value) ETL_NOEXCEPT; +``` + +These helpers convert a three-way comparison result, such as the one returned by +`etl::three_way_compare`, into a comparison category. Negative values map to `less`, positive +values to `greater`, and zero to `equal`/`equivalent`. `unordered` cannot be expressed as an +integer result, so use `etl::partial_ordering::unordered` directly for that case. + ## Example 1 ```cpp -using std::less struct Test { int a; @@ -84,6 +153,11 @@ bool operator >=(const Test& lhs, const Test& rhs) { return Compare::gte(lhs, rhs); } + +int operator <=>(const Test& lhs, const Test& rhs) +{ + return etl::three_way_compare(lhs, rhs); +} ``` ## Example 2 @@ -124,6 +198,11 @@ bool operator >=(const Test& lhs, const Test& rhs) { return Compare::gte(lhs, rhs); } + +int operator <=>(const Test& lhs, const Test& rhs) +{ + return etl::three_way_compare(lhs, rhs); +} ``` ## Example 3 @@ -158,3 +237,13 @@ bool operator >=(const Test& lhs, const Test& rhs) return Test::gte(lhs, rhs); } ``` + +## Example 4 +C++20, with or without the STL. + +```cpp +etl::strong_ordering operator <=>(const Test& lhs, const Test& rhs) +{ + return etl::make_strong_ordering(etl::three_way_compare(lhs, rhs)); +} +``` diff --git a/include/etl/chrono.h b/include/etl/chrono.h index b452ca4e..ab360108 100644 --- a/include/etl/chrono.h +++ b/include/etl/chrono.h @@ -37,6 +37,7 @@ SOFTWARE. #if ETL_USING_CPP11 + #include "compare.h" #include "hash.h" #include "integral_limits.h" #include "type_traits.h" diff --git a/include/etl/compare.h b/include/etl/compare.h index 918465bd..f909291c 100644 --- a/include/etl/compare.h +++ b/include/etl/compare.h @@ -35,6 +35,10 @@ SOFTWARE. #include "functional.h" #include "parameter_type.h" +#if ETL_USING_CPP20 && ETL_USING_STL + #include +#endif + //***************************************************************************** ///\defgroup compare compare /// Comparisons only using less than operator @@ -42,6 +46,295 @@ SOFTWARE. //***************************************************************************** namespace etl { +#if ETL_USING_CPP20 + #if ETL_USING_STL + //*************************************************************************** + /// The STL supplies the C++20 comparison categories, so simply expose them + /// through the etl namespace. This allows ETL code and user code to refer to + /// etl::strong_ordering (etc.) regardless of the STL configuration. + //*************************************************************************** + using std::partial_ordering; + using std::strong_ordering; + using std::weak_ordering; + #else + namespace private_compare + { + //************************************************************************* + /// A type that can only be constructed from the literal 0. + /// Used to constrain the comparison-category operators to comparisons + /// against the literal 0, as required by the standard. + //************************************************************************* + struct only_literal_zero + { + ETL_CONSTEXPR only_literal_zero(only_literal_zero*) ETL_NOEXCEPT {} + }; + + typedef signed char ordering_value_type; + + inline constexpr ordering_value_type ordering_less = -1; + inline constexpr ordering_value_type ordering_equivalent = 0; + inline constexpr ordering_value_type ordering_greater = 1; + inline constexpr ordering_value_type ordering_unordered = 2; + } // namespace private_compare + + //*************************************************************************** + /// Minimal partial-ordering category for C++20 three-way comparisons when the + /// STL is unavailable. + /// Unlike the other categories this one has an 'unordered' state, for which + /// every comparison against 0 is false. + //*************************************************************************** + class partial_ordering + { + public: + + static const partial_ordering less; + static const partial_ordering equivalent; + static const partial_ordering greater; + static const partial_ordering unordered; + + friend ETL_CONSTEXPR bool operator==(partial_ordering lhs, partial_ordering rhs) ETL_NOEXCEPT = default; + + friend ETL_CONSTEXPR bool operator==(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_less; + } + friend ETL_CONSTEXPR bool operator>(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_greater; + } + friend ETL_CONSTEXPR bool operator<=(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return (v.value == private_compare::ordering_less) || (v.value == private_compare::ordering_equivalent); + } + friend ETL_CONSTEXPR bool operator>=(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return (v.value == private_compare::ordering_greater) || (v.value == private_compare::ordering_equivalent); + } + + friend ETL_CONSTEXPR bool operator<(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v > 0; + } + friend ETL_CONSTEXPR bool operator>(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v < 0; + } + friend ETL_CONSTEXPR bool operator<=(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v >= 0; + } + friend ETL_CONSTEXPR bool operator>=(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v <= 0; + } + + private: + + explicit ETL_CONSTEXPR partial_ordering(private_compare::ordering_value_type value_) ETL_NOEXCEPT + : value(value_) + { + } + + friend class weak_ordering; + friend class strong_ordering; + + private_compare::ordering_value_type value; + }; + + inline constexpr partial_ordering partial_ordering::less(private_compare::ordering_less); + inline constexpr partial_ordering partial_ordering::equivalent(private_compare::ordering_equivalent); + inline constexpr partial_ordering partial_ordering::greater(private_compare::ordering_greater); + inline constexpr partial_ordering partial_ordering::unordered(private_compare::ordering_unordered); + + //*************************************************************************** + /// Minimal weak-ordering category for C++20 three-way comparisons when the + /// STL is unavailable. + //*************************************************************************** + class weak_ordering + { + public: + + static const weak_ordering less; + static const weak_ordering equivalent; + static const weak_ordering greater; + + ETL_CONSTEXPR operator partial_ordering() const ETL_NOEXCEPT + { + return partial_ordering(value); + } + + friend ETL_CONSTEXPR bool operator==(weak_ordering lhs, weak_ordering rhs) ETL_NOEXCEPT = default; + + friend ETL_CONSTEXPR bool operator==(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value < private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value > private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<=(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value <= private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>=(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value >= private_compare::ordering_equivalent; + } + + friend ETL_CONSTEXPR bool operator<(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v > 0; + } + friend ETL_CONSTEXPR bool operator>(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v < 0; + } + friend ETL_CONSTEXPR bool operator<=(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v >= 0; + } + friend ETL_CONSTEXPR bool operator>=(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v <= 0; + } + + private: + + explicit ETL_CONSTEXPR weak_ordering(private_compare::ordering_value_type value_) ETL_NOEXCEPT + : value(value_) + { + } + + friend class strong_ordering; + + private_compare::ordering_value_type value; + }; + + inline constexpr weak_ordering weak_ordering::less(private_compare::ordering_less); + inline constexpr weak_ordering weak_ordering::equivalent(private_compare::ordering_equivalent); + inline constexpr weak_ordering weak_ordering::greater(private_compare::ordering_greater); + + //*************************************************************************** + /// Minimal strong-ordering category for C++20 three-way comparisons when the + /// STL is unavailable. + //*************************************************************************** + class strong_ordering + { + public: + + static const strong_ordering less; + static const strong_ordering equal; + static const strong_ordering equivalent; + static const strong_ordering greater; + + ETL_CONSTEXPR operator partial_ordering() const ETL_NOEXCEPT + { + return partial_ordering(value); + } + + ETL_CONSTEXPR operator weak_ordering() const ETL_NOEXCEPT + { + return weak_ordering(value); + } + + friend ETL_CONSTEXPR bool operator==(strong_ordering lhs, strong_ordering rhs) ETL_NOEXCEPT = default; + + friend ETL_CONSTEXPR bool operator==(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value < private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value > private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<=(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value <= private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>=(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value >= private_compare::ordering_equivalent; + } + + friend ETL_CONSTEXPR bool operator<(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v > 0; + } + friend ETL_CONSTEXPR bool operator>(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v < 0; + } + friend ETL_CONSTEXPR bool operator<=(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v >= 0; + } + friend ETL_CONSTEXPR bool operator>=(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v <= 0; + } + + private: + + explicit ETL_CONSTEXPR strong_ordering(private_compare::ordering_value_type value_) ETL_NOEXCEPT + : value(value_) + { + } + + private_compare::ordering_value_type value; + }; + + inline constexpr strong_ordering strong_ordering::less(private_compare::ordering_less); + inline constexpr strong_ordering strong_ordering::equal(private_compare::ordering_equivalent); + inline constexpr strong_ordering strong_ordering::equivalent(private_compare::ordering_equivalent); + inline constexpr strong_ordering strong_ordering::greater(private_compare::ordering_greater); + #endif + + //*************************************************************************** + /// Converts a three-way comparison result to an etl::strong_ordering. + /// Negative values map to 'less', positive values to 'greater' and zero to + /// 'equal'. + //*************************************************************************** + ETL_NODISCARD ETL_CONSTEXPR inline etl::strong_ordering make_strong_ordering(int value) ETL_NOEXCEPT + { + return (value < 0) ? etl::strong_ordering::less : (value > 0) ? etl::strong_ordering::greater : etl::strong_ordering::equal; + } + + //*************************************************************************** + /// Converts a three-way comparison result to an etl::weak_ordering. + /// Negative values map to 'less', positive values to 'greater' and zero to + /// 'equivalent'. + //*************************************************************************** + ETL_NODISCARD ETL_CONSTEXPR inline etl::weak_ordering make_weak_ordering(int value) ETL_NOEXCEPT + { + return (value < 0) ? etl::weak_ordering::less : (value > 0) ? etl::weak_ordering::greater : etl::weak_ordering::equivalent; + } + + //*************************************************************************** + /// Converts a three-way comparison result to an etl::partial_ordering. + /// Negative values map to 'less', positive values to 'greater' and zero to + /// 'equivalent'. + /// Note that 'unordered' cannot be expressed by an integer comparison result, + /// so use etl::partial_ordering::unordered directly for that case. + //*************************************************************************** + ETL_NODISCARD ETL_CONSTEXPR inline etl::partial_ordering make_partial_ordering(int value) ETL_NOEXCEPT + { + return (value < 0) ? etl::partial_ordering::less : (value > 0) ? etl::partial_ordering::greater : etl::partial_ordering::equivalent; + } +#endif + //*************************************************************************** /// Defines <=, >, >=, ==, !=, <=> in terms of < /// Default implementation of TLess is etl::less @@ -105,7 +398,7 @@ namespace etl #endif ETL_CONSTEXPR14 int three_way_compare(const T& lhs, const T& rhs) { - return etl::compare::cmp(lhs, rhs); + return etl::compare::cmp(lhs, rhs); } } // namespace etl diff --git a/include/etl/private/chrono/day.h b/include/etl/private/chrono/day.h index 1f903067..bbca6a59 100644 --- a/include/etl/private/chrono/day.h +++ b/include/etl/private/chrono/day.h @@ -211,11 +211,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::day& d1, const etl::chrono::day& d2) ETL_NOEXCEPT { - return (static_cast(d1) <=> static_cast(d2)); + return etl::make_strong_ordering(d1.compare(d2)); } #endif diff --git a/include/etl/private/chrono/duration.h b/include/etl/private/chrono/duration.h index cf48e170..fc0b6cb3 100644 --- a/include/etl/private/chrono/duration.h +++ b/include/etl/private/chrono/duration.h @@ -482,7 +482,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 template [[nodiscard]] constexpr auto operator<=>(const etl::chrono::duration& lhs, const etl::chrono::duration& rhs) ETL_NOEXCEPT @@ -492,7 +492,37 @@ namespace etl common_t l = etl::chrono::duration_cast(lhs); common_t r = etl::chrono::duration_cast(rhs); + #if ETL_USING_STL return (l.count() <=> r.count()); + #else + // The STL returns the ordering category of the representation type. + // Mirror that: a floating point representation is only partially ordered. + if constexpr (etl::is_floating_point::value) + { + if (l.count() < r.count()) + { + return etl::partial_ordering::less; + } + else if (r.count() < l.count()) + { + return etl::partial_ordering::greater; + } + else if ((l.count() <= r.count()) && (r.count() <= l.count())) + { + // Equivalent. Expressed without '==' so that -Wfloat-equal is not triggered. + return etl::partial_ordering::equivalent; + } + else + { + // At least one of the values is NaN. + return etl::partial_ordering::unordered; + } + } + else + { + return etl::make_strong_ordering((l.count() < r.count()) ? -1 : (r.count() < l.count()) ? 1 : 0); + } + #endif } #endif diff --git a/include/etl/private/chrono/month.h b/include/etl/private/chrono/month.h index 40c2e594..b166f509 100644 --- a/include/etl/private/chrono/month.h +++ b/include/etl/private/chrono/month.h @@ -224,11 +224,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month& d1, const etl::chrono::month& d2) ETL_NOEXCEPT { - return (static_cast(d1) <=> static_cast(d2)); + return etl::make_strong_ordering(d1.compare(d2)); } #endif diff --git a/include/etl/private/chrono/month_day.h b/include/etl/private/chrono/month_day.h index f4f77628..e53f8ac8 100644 --- a/include/etl/private/chrono/month_day.h +++ b/include/etl/private/chrono/month_day.h @@ -178,7 +178,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month_day& lhs, const etl::chrono::month_day& rhs) ETL_NOEXCEPT { @@ -298,11 +298,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month_day_last& mdl1, const etl::chrono::month_day_last& mdl2) ETL_NOEXCEPT { - return (static_cast(mdl1.month()) <=> static_cast(mdl2.month())); + return mdl1.month() <=> mdl2.month(); } #endif } // namespace chrono diff --git a/include/etl/private/chrono/time_point.h b/include/etl/private/chrono/time_point.h index a0e79bb1..f90f1c61 100644 --- a/include/etl/private/chrono/time_point.h +++ b/include/etl/private/chrono/time_point.h @@ -353,7 +353,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 template [[nodiscard]] constexpr auto operator<=>(const etl::chrono::time_point& lhs, const etl::chrono::time_point& rhs) diff --git a/include/etl/private/chrono/year.h b/include/etl/private/chrono/year.h index e6cb3416..35f67853 100644 --- a/include/etl/private/chrono/year.h +++ b/include/etl/private/chrono/year.h @@ -240,11 +240,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year& y1, const etl::chrono::year& y2) ETL_NOEXCEPT { - return (static_cast(y1) <=> static_cast(y2)); + return etl::make_strong_ordering(y1.compare(y2)); } #endif diff --git a/include/etl/private/chrono/year_month.h b/include/etl/private/chrono/year_month.h index 44bb9fd1..16b3a431 100644 --- a/include/etl/private/chrono/year_month.h +++ b/include/etl/private/chrono/year_month.h @@ -234,7 +234,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month& lhs, const etl::chrono::year_month& rhs) ETL_NOEXCEPT { diff --git a/include/etl/private/chrono/year_month_day.h b/include/etl/private/chrono/year_month_day.h index 91088c3b..a0f42474 100644 --- a/include/etl/private/chrono/year_month_day.h +++ b/include/etl/private/chrono/year_month_day.h @@ -426,7 +426,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month_day& lhs, const etl::chrono::year_month_day& rhs) ETL_NOEXCEPT { @@ -722,7 +722,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month_day_last& lhs, const etl::chrono::year_month_day_last& rhs) ETL_NOEXCEPT { diff --git a/test/test_chrono_day.cpp b/test/test_chrono_day.cpp index 47ab3fc8..706a3de9 100644 --- a/test/test_chrono_day.cpp +++ b/test/test_chrono_day.cpp @@ -278,7 +278,7 @@ namespace CHECK_TRUE(day10 >= day10); CHECK_TRUE(day20 >= day10); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((day10 <=> day10) == 0); CHECK_TRUE((day10 <=> day20) < 0); CHECK_TRUE((day20 <=> day10) > 0); diff --git a/test/test_chrono_duration.cpp b/test/test_chrono_duration.cpp index 4122cdd5..1372d6f6 100644 --- a/test/test_chrono_duration.cpp +++ b/test/test_chrono_duration.cpp @@ -1045,16 +1045,41 @@ namespace CHECK_EQUAL(1, s1.count()); // 10 seconds % (-3) seconds = 1 second } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_duration_spaceship_operator) { Chrono::seconds s1(2); // 2 seconds Chrono::milliseconds ms1(1500); // 1500 milliseconds - CHECK_TRUE(s1 <=> s1 == std::strong_ordering::equal); - CHECK_TRUE(ms1 <=> s1 == std::strong_ordering::less); - CHECK_TRUE(s1 <=> ms1 == std::strong_ordering::greater); + CHECK_TRUE(s1 <=> s1 == etl::strong_ordering::equal); + CHECK_TRUE(ms1 <=> s1 == etl::strong_ordering::less); + CHECK_TRUE(s1 <=> ms1 == etl::strong_ordering::greater); + + static_assert(etl::is_same ms1), etl::strong_ordering>::value, "Must return etl::strong_ordering"); + } + + //************************************************************************* + TEST(test_duration_spaceship_operator_floating_point_representation) + { + typedef Chrono::duration fp_seconds; + + fp_seconds fs1(1.5); + fp_seconds fs2(2.5); + + // A floating point representation is only partially ordered. + static_assert(etl::is_same fs2), etl::partial_ordering>::value, "Must return etl::partial_ordering"); + + CHECK_TRUE((fs1 <=> fs1) == etl::partial_ordering::equivalent); + CHECK_TRUE((fs1 <=> fs2) == etl::partial_ordering::less); + CHECK_TRUE((fs2 <=> fs1) == etl::partial_ordering::greater); + + fp_seconds nan_seconds(etl::numeric_limits::quiet_NaN()); + + CHECK_TRUE((nan_seconds <=> fs1) == etl::partial_ordering::unordered); + CHECK_FALSE((nan_seconds <=> fs1) < 0); + CHECK_FALSE((nan_seconds <=> fs1) == 0); + CHECK_FALSE((nan_seconds <=> fs1) > 0); } #endif diff --git a/test/test_chrono_month.cpp b/test/test_chrono_month.cpp index a4e61491..46522d4f 100644 --- a/test/test_chrono_month.cpp +++ b/test/test_chrono_month.cpp @@ -303,7 +303,7 @@ namespace CHECK_TRUE(month1 >= month1); CHECK_TRUE(month2 >= month1); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((month1 <=> month1) == 0); CHECK_TRUE((month1 <=> month2) < 0); CHECK_TRUE((month2 <=> month1) > 0); diff --git a/test/test_chrono_month_day.cpp b/test/test_chrono_month_day.cpp index 7e7bf9b5..91a61482 100644 --- a/test/test_chrono_month_day.cpp +++ b/test/test_chrono_month_day.cpp @@ -89,7 +89,7 @@ namespace CHECK_FALSE(md.ok()); // Invalid month_day } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_month_day_spaceship_operator) { @@ -97,9 +97,9 @@ namespace Chrono::month_day md2{Chrono::February, Chrono::day{10}}; Chrono::month_day md3{Chrono::January, Chrono::day{15}}; - CHECK_TRUE((md1 <=> md3) == std::strong_ordering::equal); // Same month and day - CHECK_TRUE((md1 <=> md2) == std::strong_ordering::less); // Different month and day - CHECK_TRUE((md2 <=> md1) == std::strong_ordering::greater); // Same month and day + CHECK_TRUE((md1 <=> md3) == etl::strong_ordering::equal); // Same month and day + CHECK_TRUE((md1 <=> md2) == etl::strong_ordering::less); // Different month and day + CHECK_TRUE((md2 <=> md1) == etl::strong_ordering::greater); // Same month and day } #endif diff --git a/test/test_chrono_month_day_last.cpp b/test/test_chrono_month_day_last.cpp index bd5bee7f..927a9369 100644 --- a/test/test_chrono_month_day_last.cpp +++ b/test/test_chrono_month_day_last.cpp @@ -100,6 +100,20 @@ namespace CHECK_TRUE(month_day_last1 != month_day_last3); } +#if ETL_USING_CPP20 + //************************************************************************* + TEST(test_month_day_last_spaceship_operator) + { + Chrono::month_day_last month_day_last1(Chrono::January); + Chrono::month_day_last month_day_last2(Chrono::February); + Chrono::month_day_last month_day_last3(Chrono::January); + + CHECK_TRUE((month_day_last1 <=> month_day_last3) == etl::strong_ordering::equal); + CHECK_TRUE((month_day_last1 <=> month_day_last2) == etl::strong_ordering::less); + CHECK_TRUE((month_day_last2 <=> month_day_last1) == etl::strong_ordering::greater); + } +#endif + #if ETL_USING_ETL_CHRONO //************************************************************************* TEST(test_month_day_last_hashes_are_unique) diff --git a/test/test_chrono_time_point.cpp b/test/test_chrono_time_point.cpp index a4866f0c..89a00227 100644 --- a/test/test_chrono_time_point.cpp +++ b/test/test_chrono_time_point.cpp @@ -365,7 +365,7 @@ namespace CHECK_FALSE(tp10d >= tp20h); CHECK_FALSE(tp10h >= tp20d); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((tp10h <=> tp10h) == 0); CHECK_TRUE((tp10h <=> tp20h) < 0); CHECK_TRUE((tp20h <=> tp10h) > 0); @@ -377,6 +377,8 @@ namespace CHECK_TRUE((tp10d <=> tp10h) == 0); CHECK_TRUE((tp10h <=> tp20d) < 0); CHECK_TRUE((tp20d <=> tp10h) > 0); + + static_assert(etl::is_same tp20d), etl::strong_ordering>::value, "Must return etl::strong_ordering"); #endif } diff --git a/test/test_chrono_year.cpp b/test/test_chrono_year.cpp index 2b8dad29..8e3c9f1c 100644 --- a/test/test_chrono_year.cpp +++ b/test/test_chrono_year.cpp @@ -284,7 +284,7 @@ namespace CHECK_FALSE(year10 == year20); CHECK_TRUE(year10 != year20); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((year10 <=> year10) == 0); CHECK_TRUE((year10 <=> year20) < 0); CHECK_TRUE((year20 <=> year10) > 0); diff --git a/test/test_chrono_year_month.cpp b/test/test_chrono_year_month.cpp index 73065121..ad051386 100644 --- a/test/test_chrono_year_month.cpp +++ b/test/test_chrono_year_month.cpp @@ -89,7 +89,7 @@ namespace CHECK_FALSE(ym.ok()); // Invalid year_month } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_year_month_spaceship_operator) { @@ -97,11 +97,11 @@ namespace Chrono::year_month ym2{Chrono::year(2001), Chrono::January}; Chrono::year_month ym3{Chrono::year(2000), Chrono::February}; - CHECK_TRUE((ym1 <=> ym1) == std::strong_ordering::equal); - CHECK_TRUE((ym1 <=> ym2) == std::strong_ordering::less); - CHECK_TRUE((ym2 <=> ym1) == std::strong_ordering::greater); - CHECK_TRUE((ym1 <=> ym3) == std::strong_ordering::less); - CHECK_TRUE((ym3 <=> ym1) == std::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym1) == etl::strong_ordering::equal); + CHECK_TRUE((ym1 <=> ym2) == etl::strong_ordering::less); + CHECK_TRUE((ym2 <=> ym1) == etl::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym3) == etl::strong_ordering::less); + CHECK_TRUE((ym3 <=> ym1) == etl::strong_ordering::greater); } #endif diff --git a/test/test_chrono_year_month_day.cpp b/test/test_chrono_year_month_day.cpp index e44ee967..a83f7ebe 100644 --- a/test/test_chrono_year_month_day.cpp +++ b/test/test_chrono_year_month_day.cpp @@ -98,7 +98,7 @@ namespace CHECK_FALSE(ymd.ok()); // Invalid year_month_day } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_year_month_day_spaceship_operator) { @@ -107,13 +107,13 @@ namespace Chrono::year_month_day ym3{Chrono::year(2000), Chrono::February, Chrono::day(1)}; Chrono::year_month_day ym4{Chrono::year(2000), Chrono::January, Chrono::day(2)}; - CHECK_TRUE((ym1 <=> ym1) == std::strong_ordering::equal); - CHECK_TRUE((ym1 <=> ym2) == std::strong_ordering::less); - CHECK_TRUE((ym2 <=> ym1) == std::strong_ordering::greater); - CHECK_TRUE((ym1 <=> ym3) == std::strong_ordering::less); - CHECK_TRUE((ym3 <=> ym1) == std::strong_ordering::greater); - CHECK_TRUE((ym1 <=> ym4) == std::strong_ordering::less); - CHECK_TRUE((ym4 <=> ym1) == std::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym1) == etl::strong_ordering::equal); + CHECK_TRUE((ym1 <=> ym2) == etl::strong_ordering::less); + CHECK_TRUE((ym2 <=> ym1) == etl::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym3) == etl::strong_ordering::less); + CHECK_TRUE((ym3 <=> ym1) == etl::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym4) == etl::strong_ordering::less); + CHECK_TRUE((ym4 <=> ym1) == etl::strong_ordering::greater); } #endif diff --git a/test/test_chrono_year_month_day_last.cpp b/test/test_chrono_year_month_day_last.cpp index 53a556af..8edaca2f 100644 --- a/test/test_chrono_year_month_day_last.cpp +++ b/test/test_chrono_year_month_day_last.cpp @@ -160,6 +160,23 @@ namespace CHECK_TRUE(year_month_day_last1 != year_month_day_last3); } +#if ETL_USING_CPP20 + //************************************************************************* + TEST(test_year_month_day_last_spaceship_operator) + { + Chrono::year_month_day_last year_month_day_last1(Chrono::year(2000), Chrono::month_day_last(Chrono::January)); + Chrono::year_month_day_last year_month_day_last2(Chrono::year(2001), Chrono::month_day_last(Chrono::January)); + Chrono::year_month_day_last year_month_day_last3(Chrono::year(2000), Chrono::month_day_last(Chrono::February)); + Chrono::year_month_day_last year_month_day_last4(Chrono::year(2000), Chrono::month_day_last(Chrono::January)); + + CHECK_TRUE((year_month_day_last1 <=> year_month_day_last4) == etl::strong_ordering::equal); + CHECK_TRUE((year_month_day_last1 <=> year_month_day_last2) == etl::strong_ordering::less); + CHECK_TRUE((year_month_day_last2 <=> year_month_day_last1) == etl::strong_ordering::greater); + CHECK_TRUE((year_month_day_last1 <=> year_month_day_last3) == etl::strong_ordering::less); + CHECK_TRUE((year_month_day_last3 <=> year_month_day_last1) == etl::strong_ordering::greater); + } +#endif + #if ETL_USING_ETL_CHRONO //************************************************************************* TEST(test_year_month_day_last_hashes_are_unique) diff --git a/test/test_compare.cpp b/test/test_compare.cpp index 032cbd41..d0ec3b7c 100644 --- a/test/test_compare.cpp +++ b/test/test_compare.cpp @@ -91,6 +91,20 @@ namespace } }; + struct ComparatorOnlyObject + { + int a; + int b; + }; + + struct LessBySecond + { + bool constexpr operator()(const ComparatorOnlyObject& lhs, const ComparatorOnlyObject& rhs) const + { + return lhs.b < rhs.b; + } + }; + typedef etl::compare CompareTest; SUITE(test_compare) @@ -191,5 +205,139 @@ namespace CHECK_EQUAL(CompareInt::Greater, cmp_test_greater); CHECK_EQUAL(CompareInt::Equal, cmp_test_equal); } + +#if ETL_USING_CPP20 + //************************************************************************* + TEST(test_strong_ordering) + { + constexpr etl::strong_ordering ordering_less(etl::strong_ordering::less); + constexpr etl::strong_ordering ordering_equal(etl::strong_ordering::equal); + constexpr etl::strong_ordering ordering_greater(etl::strong_ordering::greater); + + CHECK(ordering_less < 0); + CHECK(ordering_less <= 0); + CHECK(!(ordering_less == 0)); + CHECK(!(ordering_less > 0)); + CHECK(ordering_equal == 0); + CHECK(ordering_equal <= 0); + CHECK(ordering_equal >= 0); + CHECK(ordering_greater > 0); + CHECK(ordering_greater >= 0); + CHECK(!(ordering_greater == 0)); + + // Reversed operands. + CHECK(0 > ordering_less); + CHECK(0 < ordering_greater); + + static_assert(etl::strong_ordering::equivalent == etl::strong_ordering::equal, "Equivalent must alias equal"); + static_assert(etl::is_same::value, + "Must convert to weak_ordering"); + CHECK(etl::weak_ordering(etl::strong_ordering::less) < 0); + CHECK(etl::partial_ordering(etl::strong_ordering::greater) > 0); + } + + //************************************************************************* + TEST(test_make_strong_ordering) + { + constexpr etl::strong_ordering ordering_less = etl::make_strong_ordering(-1); + constexpr etl::strong_ordering ordering_equal = etl::make_strong_ordering(0); + constexpr etl::strong_ordering ordering_greater = etl::make_strong_ordering(1); + + CHECK(ordering_less < 0); + CHECK(ordering_equal == 0); + CHECK(ordering_greater > 0); + + static_assert(etl::is_same::value, "Must return etl::strong_ordering"); + } + + //************************************************************************* + TEST(test_weak_ordering) + { + constexpr etl::weak_ordering ordering_less(etl::weak_ordering::less); + constexpr etl::weak_ordering ordering_equivalent(etl::weak_ordering::equivalent); + constexpr etl::weak_ordering ordering_greater(etl::weak_ordering::greater); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + CHECK(0 > ordering_less); + CHECK(0 < ordering_greater); + + static_assert(etl::weak_ordering::equivalent == 0, "Equivalent must be zero"); + CHECK(etl::partial_ordering(etl::weak_ordering::less) < 0); + } + + //************************************************************************* + TEST(test_make_weak_ordering) + { + constexpr etl::weak_ordering ordering_less = etl::make_weak_ordering(-1); + constexpr etl::weak_ordering ordering_equivalent = etl::make_weak_ordering(0); + constexpr etl::weak_ordering ordering_greater = etl::make_weak_ordering(1); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + static_assert(etl::is_same::value, "Must return etl::weak_ordering"); + } + + //************************************************************************* + TEST(test_partial_ordering) + { + constexpr etl::partial_ordering ordering_less(etl::partial_ordering::less); + constexpr etl::partial_ordering ordering_equivalent(etl::partial_ordering::equivalent); + constexpr etl::partial_ordering ordering_greater(etl::partial_ordering::greater); + constexpr etl::partial_ordering ordering_unordered(etl::partial_ordering::unordered); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + // An unordered result compares false against zero in every direction. + CHECK(!(ordering_unordered < 0)); + CHECK(!(ordering_unordered <= 0)); + CHECK(!(ordering_unordered == 0)); + CHECK(!(ordering_unordered >= 0)); + CHECK(!(ordering_unordered > 0)); + CHECK(ordering_unordered != 0); + + static_assert(etl::partial_ordering::equivalent == 0, "Equivalent must be zero"); + } + + //************************************************************************* + TEST(test_make_partial_ordering) + { + constexpr etl::partial_ordering ordering_less = etl::make_partial_ordering(-1); + constexpr etl::partial_ordering ordering_equivalent = etl::make_partial_ordering(0); + constexpr etl::partial_ordering ordering_greater = etl::make_partial_ordering(1); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + static_assert(etl::is_same::value, "Must return etl::partial_ordering"); + } + + //************************************************************************* + TEST(test_three_way_compare_custom_less) + { + constexpr ComparatorOnlyObject lhs = {5, 1}; + constexpr ComparatorOnlyObject rhs = {1, 2}; + constexpr ComparatorOnlyObject eq = {9, 1}; + + constexpr int cmp_less = etl::three_way_compare(lhs, rhs); + constexpr int cmp_greater = etl::three_way_compare(rhs, lhs); + constexpr int cmp_equal = etl::three_way_compare(lhs, eq); + + CHECK_EQUAL(-1, cmp_less); + CHECK_EQUAL(1, cmp_greater); + CHECK_EQUAL(0, cmp_equal); + + CHECK(etl::make_strong_ordering(cmp_less) < 0); + CHECK(etl::make_strong_ordering(cmp_greater) > 0); + CHECK(etl::make_strong_ordering(cmp_equal) == 0); + } +#endif } } // namespace