[libcxx-commits] [libcxx] b80f2df - [libc++][spaceship] Implement std::tuple::operator<=>
Kent Ross via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Oct 8 16:30:11 PDT 2021
Author: Kent Ross
Date: 2021-10-08T16:24:28-07:00
New Revision: b80f2dfd11e2d73852f9f4fc96700fc6517e4e81
URL: https://github.com/llvm/llvm-project/commit/b80f2dfd11e2d73852f9f4fc96700fc6517e4e81
DIFF: https://github.com/llvm/llvm-project/commit/b80f2dfd11e2d73852f9f4fc96700fc6517e4e81.diff
LOG: [libc++][spaceship] Implement std::tuple::operator<=>
Implement parts of P1614, including three-way comparison for tuples, and expand testing.
Reviewed By: ldionne, Mordante, #libc
Differential Revision: https://reviews.llvm.org/D108250
Added:
libcxx/test/std/library/description/conventions/expos.only.func/synth_three_way.pass.cpp
libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp
libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp
Modified:
libcxx/docs/Status/SpaceshipProjects.csv
libcxx/include/tuple
Removed:
libcxx/test/libcxx/library/description/conventions/expos.only.func/synth_three_way.pass.cpp
################################################################################
diff --git a/libcxx/docs/Status/SpaceshipProjects.csv b/libcxx/docs/Status/SpaceshipProjects.csv
index 0240ff58cc4d8..f517e2bee6dad 100644
--- a/libcxx/docs/Status/SpaceshipProjects.csv
+++ b/libcxx/docs/Status/SpaceshipProjects.csv
@@ -14,7 +14,7 @@ Section,Description,Dependencies,Assignee,Complete
| `[syserr.errcat.nonvirtuals] <https://wg21.link/syserr.errcat.nonvirtuals>`_,| error_category,[comparisons.three.way],Unassigned,|Not Started|
| `[syserr.compare] <https://wg21.link/syserr.compare>`_,"| error_code
| error_condition",None,Unassigned,|Not Started|
-| `[tuple.rel] <https://wg21.link/tuple.rel>`_,| `tuple <https://reviews.llvm.org/D108250>`_,[expos.only.func],Kent Ross,|In Progress|
+| `[tuple.rel] <https://wg21.link/tuple.rel>`_,| `tuple <https://reviews.llvm.org/D108250>`_,[expos.only.func],Kent Ross,|Complete|
"| `[optional.relops] <https://wg21.link/optional.relops>`_
| `[optional.nullops] <https://wg21.link/optional.nullops>`_
| `[optional.comp.with.t] <https://wg21.link/optional.comp.with.t>`_","| optional
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 93b6e60498958..50d31149ee099 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -132,11 +132,14 @@ template <class T1, class... T>
// 20.4.1.6, relational operators:
template<class... T, class... U> bool operator==(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
-template<class... T, class... U> bool operator<(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
-template<class... T, class... U> bool operator!=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
-template<class... T, class... U> bool operator>(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
-template<class... T, class... U> bool operator<=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
-template<class... T, class... U> bool operator>=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
+template<class... T, class... U> bool operator<(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
+template<class... T, class... U> bool operator!=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
+template<class... T, class... U> bool operator>(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
+template<class... T, class... U> bool operator<=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
+template<class... T, class... U> bool operator>=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
+template<class... T, class... U>
+ constexpr common_comparison_category_t<synth-three-way-result<T, U>...>
+ operator<=>(const tuple<T...>&, const tuple<U...>&); // since C++20
template <class... Types, class Alloc>
struct uses_allocator<tuple<Types...>, Alloc>;
@@ -149,6 +152,8 @@ template <class... Types>
*/
+#include <__compare/common_comparison_category.h>
+#include <__compare/synth_three_way.h>
#include <__config>
#include <__functional/unwrap_ref.h>
#include <__functional_base>
@@ -156,6 +161,7 @@ template <class... Types>
#include <__memory/uses_allocator.h>
#include <__tuple>
#include <__utility/forward.h>
+#include <__utility/integer_sequence.h>
#include <__utility/move.h>
#include <compare>
#include <cstddef>
@@ -1300,6 +1306,30 @@ operator==(const tuple<_Tp...>& __x, const tuple<_Up...>& __y)
return __tuple_equal<sizeof...(_Tp)>()(__x, __y);
}
+#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+// operator<=>
+
+template <class ..._Tp, class ..._Up, size_t ..._Is>
+_LIBCPP_HIDE_FROM_ABI constexpr
+auto
+__tuple_compare_three_way(const tuple<_Tp...>& __x, const tuple<_Up...>& __y, index_sequence<_Is...>) {
+ common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...> __result = strong_ordering::equal;
+ static_cast<void>(((__result = _VSTD::__synth_three_way(_VSTD::get<_Is>(__x), _VSTD::get<_Is>(__y)), __result != 0) || ...));
+ return __result;
+}
+
+template <class ..._Tp, class ..._Up>
+requires (sizeof...(_Tp) == sizeof...(_Up))
+_LIBCPP_HIDE_FROM_ABI constexpr
+common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>
+operator<=>(const tuple<_Tp...>& __x, const tuple<_Up...>& __y)
+{
+ return _VSTD::__tuple_compare_three_way(__x, __y, index_sequence_for<_Tp...>{});
+}
+
+#else // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
template <class ..._Tp, class ..._Up>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
bool
@@ -1368,6 +1398,8 @@ operator<=(const tuple<_Tp...>& __x, const tuple<_Up...>& __y)
return !(__y < __x);
}
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
// tuple_cat
template <class _Tp, class _Up> struct __tuple_cat_type;
diff --git a/libcxx/test/libcxx/library/description/conventions/expos.only.func/synth_three_way.pass.cpp b/libcxx/test/libcxx/library/description/conventions/expos.only.func/synth_three_way.pass.cpp
deleted file mode 100644
index a03cd2bbaea21..0000000000000
--- a/libcxx/test/libcxx/library/description/conventions/expos.only.func/synth_three_way.pass.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts
-// ADDITIONAL_COMPILE_FLAGS: -Wno-sign-compare
-
-// constexpr auto __synth_three_way = ...;
-
-#include <cassert>
-#include <compare>
-#include <limits>
-#include <utility> // Includes synth-three-way via std::pair::operator<=>
-
-#include "test_macros.h"
-
-template <typename T> concept can_synth_three_way = requires(T t) { std::__synth_three_way(t, t); };
-
-// A custom three-way result type
-struct CustomEquality {
- friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
- friend constexpr bool operator<(const CustomEquality&, int) noexcept { return false; }
- friend constexpr bool operator<(int, const CustomEquality&) noexcept { return false; }
-};
-
-constexpr bool test() {
- {
- assert(std::__synth_three_way(1, 1) == std::strong_ordering::equal);
- assert(std::__synth_three_way(2, 1) == std::strong_ordering::greater);
- assert(std::__synth_three_way(1, 2) == std::strong_ordering::less);
- ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<int, int>);
- ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<short, long long int>);
- }
- {
- constexpr double nan = std::numeric_limits<double>::quiet_NaN();
- assert(std::__synth_three_way(1.0, 1.0) == std::partial_ordering::equivalent);
- assert(std::__synth_three_way(2.0, 1.0) == std::partial_ordering::greater);
- assert(std::__synth_three_way(1.0, 2.0) == std::partial_ordering::less);
- assert(std::__synth_three_way(nan, nan) == std::partial_ordering::unordered);
- ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<double, double>);
- ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<double, float>);
- ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<double, int>);
- ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<float, short>);
- }
- {
- struct StrongSpaceship {
- int value;
- constexpr bool operator==(const StrongSpaceship&) const = default;
- constexpr std::strong_ordering operator<=>(const StrongSpaceship& other) const { return value <=> other.value; }
- };
- assert(std::__synth_three_way(StrongSpaceship{1}, StrongSpaceship{1}) == std::strong_ordering::equal);
- assert(std::__synth_three_way(StrongSpaceship{2}, StrongSpaceship{1}) == std::strong_ordering::greater);
- assert(std::__synth_three_way(StrongSpaceship{1}, StrongSpaceship{2}) == std::strong_ordering::less);
- ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<StrongSpaceship, StrongSpaceship>);
- }
- {
- struct WeakSpaceship {
- int value;
- constexpr bool operator==(const WeakSpaceship&) const = default;
- constexpr std::weak_ordering operator<=>(const WeakSpaceship& other) const {
- return value <=> other.value;
- }
- };
- assert(std::__synth_three_way(WeakSpaceship{1}, WeakSpaceship{1}) == std::weak_ordering::equivalent);
- assert(std::__synth_three_way(WeakSpaceship{2}, WeakSpaceship{1}) == std::weak_ordering::greater);
- assert(std::__synth_three_way(WeakSpaceship{1}, WeakSpaceship{2}) == std::weak_ordering::less);
- ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<WeakSpaceship, WeakSpaceship>);
- }
- {
- struct PartialSpaceship {
- double value;
- constexpr bool operator==(const PartialSpaceship&) const = default;
- constexpr std::partial_ordering operator<=>(const PartialSpaceship& other) const {
- return value <=> other.value;
- }
- };
- constexpr double nan = std::numeric_limits<double>::quiet_NaN();
- assert(std::__synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{1.0}) == std::partial_ordering::equivalent);
- assert(std::__synth_three_way(PartialSpaceship{2.0}, PartialSpaceship{1.0}) == std::partial_ordering::greater);
- assert(std::__synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{2.0}) == std::partial_ordering::less);
- assert(std::__synth_three_way(PartialSpaceship{nan}, PartialSpaceship{nan}) == std::partial_ordering::unordered);
- ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<PartialSpaceship, PartialSpaceship>);
- }
- {
- struct NoSpaceship {
- int value;
- constexpr bool operator==(const NoSpaceship&) const = default;
- constexpr bool operator<(const NoSpaceship& other) const { return value < other.value; }
- };
- assert(std::__synth_three_way(NoSpaceship{1}, NoSpaceship{1}) == std::weak_ordering::equivalent);
- assert(std::__synth_three_way(NoSpaceship{2}, NoSpaceship{1}) == std::weak_ordering::greater);
- assert(std::__synth_three_way(NoSpaceship{1}, NoSpaceship{2}) == std::weak_ordering::less);
- ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<NoSpaceship, NoSpaceship>);
- }
- {
- // Types with operator<=> but no operator== are not three_way_comparable and will fall back to operator< and
- // compare as weakly ordered.
- struct SpaceshipNoEquals {
- constexpr std::strong_ordering operator<=>(const SpaceshipNoEquals&) const {
- return std::strong_ordering::equivalent;
- }
- };
- assert(std::__synth_three_way(SpaceshipNoEquals{}, SpaceshipNoEquals{}) == std::weak_ordering::equivalent);
- ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<SpaceshipNoEquals, SpaceshipNoEquals>);
- }
- {
- // Custom three-way-comparison result types cannot satisfy standard concepts (and therefore synth-three-way)
- // because they are not understood by std::common_comparison_category, but they can still be used in
- // the same way as standard orderings to do comparisons, and thus can be used by synth-three-way to yield a
- // weakly-ordered result.
- struct CustomSpaceship {
- constexpr CustomEquality operator<=>(const CustomSpaceship&) const { return CustomEquality(); }
- };
- assert((CustomSpaceship() <=> CustomSpaceship()) == 0);
- assert(!(CustomSpaceship() < CustomSpaceship()));
- assert(std::__synth_three_way(CustomSpaceship(), CustomSpaceship()) == std::weak_ordering::equivalent);
- ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<CustomSpaceship, CustomSpaceship>);
- }
- // SFINAE tests demonstrating synth-three-way needs three_way_comparable or operator<.
- {
- struct NoRelative {
- constexpr bool operator==(const NoRelative&) const;
- };
- static_assert(!can_synth_three_way<NoRelative>);
- }
- {
- struct NoLessThan {
- constexpr bool operator==(const NoLessThan&) const;
- constexpr bool operator>(const NoLessThan&) const;
- constexpr bool operator>=(const NoLessThan&) const;
- constexpr bool operator<=(const NoLessThan&) const;
- };
- static_assert(!can_synth_three_way<NoLessThan>);
- }
- {
- assert(std::__synth_three_way(1, 1U) == std::weak_ordering::equivalent);
- assert(std::__synth_three_way(-1, 0U) == std::weak_ordering::greater);
- // Even with the warning suppressed (-Wno-sign-compare) there should still be no <=> operator
- // between signed and unsigned types, so we should end up with a synthesized weak ordering.
- ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<int, unsigned int>);
- // When an unsigned type can be narrowed to a larger signed type, <=> should be defined and we
- // should get a strong ordering. (This probably does not raise a warning due to safe narrowing.)
- assert((static_cast<long long int>(-1) <=> static_cast<unsigned char>(0)) == std::strong_ordering::less);
- assert(std::__synth_three_way(static_cast<long long int>(-1),
- static_cast<unsigned char>(0)) == std::strong_ordering::less);
- ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<long long int, unsigned char>);
- }
-
- return true;
-}
-
-int main(int, char**) {
- test();
- static_assert(test());
-
- {
- constexpr double nan = std::numeric_limits<double>::quiet_NaN();
- assert(std::__synth_three_way(nan, 1.0) == std::partial_ordering::unordered);
- }
-
- return 0;
-}
diff --git a/libcxx/test/std/library/description/conventions/expos.only.func/synth_three_way.pass.cpp b/libcxx/test/std/library/description/conventions/expos.only.func/synth_three_way.pass.cpp
new file mode 100644
index 0000000000000..8fbe321eadd50
--- /dev/null
+++ b/libcxx/test/std/library/description/conventions/expos.only.func/synth_three_way.pass.cpp
@@ -0,0 +1,179 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts
+// ADDITIONAL_COMPILE_FLAGS: -Wno-sign-compare
+
+// constexpr auto synth-three-way = ...;
+// via std::tuple<T>(t) <=> std::tuple<U>(u), which exposes its behavior most directly
+
+#include <cassert>
+#include <compare>
+#include <limits> // quiet_NaN
+#include <tuple>
+#include <utility> // declval
+
+#include "test_macros.h"
+
+template <typename T, typename U = T>
+concept can_synth_three_way = requires(T t, U u) { std::tuple<T>(t) <=> std::tuple<U>(u); };
+
+template <typename T, typename U>
+constexpr auto synth_three_way(const T& t, const U& u) {
+ return std::tuple<T>(t) <=> std::tuple<U>(u);
+}
+
+template <typename T, typename U>
+using synth_three_way_result = decltype(std::declval<std::tuple<T>>() <=> std::declval<std::tuple<U>>());
+
+// A custom three-way result type
+struct CustomEquality {
+ friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
+ friend constexpr bool operator<(const CustomEquality&, int) noexcept { return false; }
+ friend constexpr bool operator<(int, const CustomEquality&) noexcept { return false; }
+};
+
+constexpr bool test() {
+ {
+ assert(synth_three_way(1, 1) == std::strong_ordering::equal);
+ assert(synth_three_way(2, 1) == std::strong_ordering::greater);
+ assert(synth_three_way(1, 2) == std::strong_ordering::less);
+ ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<int, int>);
+ ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<short, long long int>);
+ }
+ {
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ assert(synth_three_way(1.0, 1.0) == std::partial_ordering::equivalent);
+ assert(synth_three_way(2.0, 1.0) == std::partial_ordering::greater);
+ assert(synth_three_way(1.0, 2.0) == std::partial_ordering::less);
+ assert(synth_three_way(nan, nan) == std::partial_ordering::unordered);
+ ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, double>);
+ ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, float>);
+ ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, int>);
+ ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<float, short>);
+ }
+ {
+ struct StrongSpaceship {
+ int value;
+ constexpr bool operator==(const StrongSpaceship&) const = default;
+ constexpr std::strong_ordering operator<=>(const StrongSpaceship& other) const { return value <=> other.value; }
+ };
+ assert(synth_three_way(StrongSpaceship{1}, StrongSpaceship{1}) == std::strong_ordering::equal);
+ assert(synth_three_way(StrongSpaceship{2}, StrongSpaceship{1}) == std::strong_ordering::greater);
+ assert(synth_three_way(StrongSpaceship{1}, StrongSpaceship{2}) == std::strong_ordering::less);
+ ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<StrongSpaceship, StrongSpaceship>);
+ }
+ {
+ struct WeakSpaceship {
+ int value;
+ constexpr bool operator==(const WeakSpaceship&) const = default;
+ constexpr std::weak_ordering operator<=>(const WeakSpaceship& other) const {
+ return value <=> other.value;
+ }
+ };
+ assert(synth_three_way(WeakSpaceship{1}, WeakSpaceship{1}) == std::weak_ordering::equivalent);
+ assert(synth_three_way(WeakSpaceship{2}, WeakSpaceship{1}) == std::weak_ordering::greater);
+ assert(synth_three_way(WeakSpaceship{1}, WeakSpaceship{2}) == std::weak_ordering::less);
+ ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<WeakSpaceship, WeakSpaceship>);
+ }
+ {
+ struct PartialSpaceship {
+ double value;
+ constexpr bool operator==(const PartialSpaceship&) const = default;
+ constexpr std::partial_ordering operator<=>(const PartialSpaceship& other) const {
+ return value <=> other.value;
+ }
+ };
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ assert(synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{1.0}) == std::partial_ordering::equivalent);
+ assert(synth_three_way(PartialSpaceship{2.0}, PartialSpaceship{1.0}) == std::partial_ordering::greater);
+ assert(synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{2.0}) == std::partial_ordering::less);
+ assert(synth_three_way(PartialSpaceship{nan}, PartialSpaceship{nan}) == std::partial_ordering::unordered);
+ ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<PartialSpaceship, PartialSpaceship>);
+ }
+ {
+ struct NoSpaceship {
+ int value;
+ constexpr bool operator==(const NoSpaceship&) const = default;
+ constexpr bool operator<(const NoSpaceship& other) const { return value < other.value; }
+ };
+ assert(synth_three_way(NoSpaceship{1}, NoSpaceship{1}) == std::weak_ordering::equivalent);
+ assert(synth_three_way(NoSpaceship{2}, NoSpaceship{1}) == std::weak_ordering::greater);
+ assert(synth_three_way(NoSpaceship{1}, NoSpaceship{2}) == std::weak_ordering::less);
+ ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<NoSpaceship, NoSpaceship>);
+ }
+ {
+ // Types with operator<=> but no operator== are not three_way_comparable and will fall back to operator< and
+ // compare as weakly ordered.
+ struct SpaceshipNoEquals {
+ constexpr std::strong_ordering operator<=>(const SpaceshipNoEquals&) const {
+ return std::strong_ordering::equivalent;
+ }
+ };
+ assert(synth_three_way(SpaceshipNoEquals{}, SpaceshipNoEquals{}) == std::weak_ordering::equivalent);
+ ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<SpaceshipNoEquals, SpaceshipNoEquals>);
+ }
+ {
+ // Custom three-way-comparison result types cannot satisfy standard concepts (and therefore synth-three-way)
+ // because they are not understood by std::common_comparison_category, but they can still be used in
+ // the same way as standard orderings to do comparisons, and thus can be used by synth-three-way to yield a
+ // weakly-ordered result.
+ struct CustomSpaceship {
+ constexpr CustomEquality operator<=>(const CustomSpaceship&) const { return CustomEquality(); }
+ };
+ assert((CustomSpaceship{} <=> CustomSpaceship{}) == 0);
+ assert(!(CustomSpaceship{} < CustomSpaceship{}));
+ assert(synth_three_way(CustomSpaceship{}, CustomSpaceship{}) == std::weak_ordering::equivalent);
+ ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<CustomSpaceship, CustomSpaceship>);
+ }
+ // SFINAE tests demonstrating synth-three-way needs three_way_comparable or operator<.
+ {
+ struct NoRelative {
+ constexpr bool operator==(const NoRelative&) const;
+ };
+ static_assert(!can_synth_three_way<NoRelative>);
+ }
+ {
+ struct NoLessThan {
+ constexpr bool operator==(const NoLessThan&) const;
+ constexpr bool operator>(const NoLessThan&) const;
+ constexpr bool operator>=(const NoLessThan&) const;
+ constexpr bool operator<=(const NoLessThan&) const;
+ };
+ static_assert(!can_synth_three_way<NoLessThan>);
+ }
+ {
+ assert(synth_three_way(1, 1U) == std::weak_ordering::equivalent);
+ assert(synth_three_way(-1, 0U) == std::weak_ordering::greater);
+ // Even with the warning suppressed (-Wno-sign-compare) there should still be no <=> operator
+ // between signed and unsigned types, so we should end up with a synthesized weak ordering.
+ ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<int, unsigned int>);
+ // When an unsigned type can be narrowed to a larger signed type, <=> should be defined and we
+ // should get a strong ordering. (This probably does not raise a warning due to safe narrowing.)
+ assert(synth_three_way(static_cast<long long int>(-1), static_cast<unsigned char>(0)) == std::strong_ordering::less);
+ assert(synth_three_way(static_cast<long long int>(-1), static_cast<unsigned char>(0)) == std::strong_ordering::less);
+ ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<long long int, unsigned char>);
+ }
+#ifdef TEST_COMPILER_GCC
+ // GCC cannot evaluate NaN @ non-NaN constexpr, so test that runtime-only.
+ if (!std::is_constant_evaluated())
+#endif
+ {
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ assert(synth_three_way(nan, 1.0) == std::partial_ordering::unordered);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
new file mode 100644
index 0000000000000..851f6fcd1fbac
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types> class tuple;
+
+// template<class... TTypes, class... UTypes>
+// bool
+// operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+// template<class... TTypes, class... UTypes>
+// bool
+// operator<(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+
+// UNSUPPORTED: c++03
+
+#include <tuple>
+
+void f(std::tuple<int> t1, std::tuple<int, long> t2) {
+ // We test only the core comparison operators and trust that the others
+ // fall back on the same implementations prior to C++20.
+ static_cast<void>(t1 == t2); // expected-error@*:* {{}}
+ static_cast<void>(t1 < t2); // expected-error@*:* {{}}
+}
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp
new file mode 100644
index 0000000000000..257a3a2fcbcfe
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types> class tuple;
+
+// template<class... TTypes, class... UTypes>
+// auto
+// operator<=>(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts
+
+#include <tuple>
+
+template <class T, class U>
+concept can_compare = requires(T t, U u) { t <=> u; };
+
+typedef std::tuple<int> T1;
+typedef std::tuple<int, long> T2;
+
+static_assert(!can_compare<T1, T2>);
+static_assert(!can_compare<T2, T1>);
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp
new file mode 100644
index 0000000000000..4cd64807201b3
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp
@@ -0,0 +1,232 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types> class tuple;
+
+// template<class... TTypes, class... UTypes>
+// auto
+// operator<=>(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts
+// ADDITIONAL_COMPILE_FLAGS: -Wno-sign-compare
+
+#include <cassert>
+#include <compare>
+#include <limits>
+#include <tuple>
+#include <type_traits> // std::is_constant_evaluated
+
+#include "test_macros.h"
+
+// A custom three-way result type
+struct CustomEquality {
+ friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
+ friend constexpr bool operator<(const CustomEquality&, int) noexcept { return false; }
+ friend constexpr bool operator<(int, const CustomEquality&) noexcept { return false; }
+};
+
+constexpr bool test() {
+ // Empty tuple
+ {
+ typedef std::tuple<> T0;
+ // No member types yields strong ordering (all are equal).
+ ASSERT_SAME_TYPE(decltype(T0() <=> T0()), std::strong_ordering);
+ assert((T0() <=> T0()) == std::strong_ordering::equal);
+ }
+ // Mixed types with integers, which compare strongly ordered
+ {
+ typedef std::tuple<long> T1;
+ typedef std::tuple<short> T2;
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
+ assert((T1(1) <=> T2(1)) == std::strong_ordering::equal);
+ assert((T1(1) <=> T2(0)) == std::strong_ordering::greater);
+ assert((T1(1) <=> T2(2)) == std::strong_ordering::less);
+ }
+ {
+ typedef std::tuple<long, unsigned int> T1;
+ typedef std::tuple<short, unsigned long> T2;
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
+ assert((T1(1, 2) <=> T2(1, 2)) == std::strong_ordering::equal);
+ assert((T1(1, 2) <=> T2(0, 2)) == std::strong_ordering::greater);
+ assert((T1(1, 2) <=> T2(2, 2)) == std::strong_ordering::less);
+ assert((T1(1, 2) <=> T2(1, 1)) == std::strong_ordering::greater);
+ assert((T1(1, 2) <=> T2(1, 3)) == std::strong_ordering::less);
+ }
+ {
+ typedef std::tuple<long, int, unsigned short> T1;
+ typedef std::tuple<short, long, unsigned int> T2;
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
+ assert((T1(1, 2, 3) <=> T2(1, 2, 3)) == std::strong_ordering::equal);
+ assert((T1(1, 2, 3) <=> T2(0, 2, 3)) == std::strong_ordering::greater);
+ assert((T1(1, 2, 3) <=> T2(2, 2, 3)) == std::strong_ordering::less);
+ assert((T1(1, 2, 3) <=> T2(1, 1, 3)) == std::strong_ordering::greater);
+ assert((T1(1, 2, 3) <=> T2(1, 3, 3)) == std::strong_ordering::less);
+ assert((T1(1, 2, 3) <=> T2(1, 2, 2)) == std::strong_ordering::greater);
+ assert((T1(1, 2, 3) <=> T2(1, 2, 4)) == std::strong_ordering::less);
+ }
+ // Mixed types with floating point, which compare partially ordered
+ {
+ typedef std::tuple<long> T1;
+ typedef std::tuple<double> T2;
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
+ assert((T1(1) <=> T2(1)) == std::partial_ordering::equivalent);
+ assert((T1(1) <=> T2(0.9)) == std::partial_ordering::greater);
+ assert((T1(1) <=> T2(1.1)) == std::partial_ordering::less);
+ }
+ {
+ typedef std::tuple<long, float> T1;
+ typedef std::tuple<double, unsigned int> T2;
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
+ assert((T1(1, 2) <=> T2(1, 2)) == std::partial_ordering::equivalent);
+ assert((T1(1, 2) <=> T2(0.9, 2)) == std::partial_ordering::greater);
+ assert((T1(1, 2) <=> T2(1.1, 2)) == std::partial_ordering::less);
+ assert((T1(1, 2) <=> T2(1, 1)) == std::partial_ordering::greater);
+ assert((T1(1, 2) <=> T2(1, 3)) == std::partial_ordering::less);
+ }
+ {
+ typedef std::tuple<short, float, double> T1;
+ typedef std::tuple<double, long, unsigned int> T2;
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
+ assert((T1(1, 2, 3) <=> T2(1, 2, 3)) == std::partial_ordering::equivalent);
+ assert((T1(1, 2, 3) <=> T2(0.9, 2, 3)) == std::partial_ordering::greater);
+ assert((T1(1, 2, 3) <=> T2(1.1, 2, 3)) == std::partial_ordering::less);
+ assert((T1(1, 2, 3) <=> T2(1, 1, 3)) == std::partial_ordering::greater);
+ assert((T1(1, 2, 3) <=> T2(1, 3, 3)) == std::partial_ordering::less);
+ assert((T1(1, 2, 3) <=> T2(1, 2, 2)) == std::partial_ordering::greater);
+ assert((T1(1, 2, 3) <=> T2(1, 2, 4)) == std::partial_ordering::less);
+ }
+ {
+ typedef std::tuple<float> T1;
+ typedef std::tuple<double> T2;
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ // Comparisons with NaN and non-NaN are non-constexpr in GCC, so both sides must be NaN
+ assert((T1(nan) <=> T2(nan)) == std::partial_ordering::unordered);
+ }
+ {
+ typedef std::tuple<double, double> T1;
+ typedef std::tuple<float, float> T2;
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ assert((T1(nan, 2) <=> T2(nan, 2)) == std::partial_ordering::unordered);
+ assert((T1(1, nan) <=> T2(1, nan)) == std::partial_ordering::unordered);
+ }
+ {
+ typedef std::tuple<double, float, float> T1;
+ typedef std::tuple<double, double, float> T2;
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ assert((T1(nan, 2, 3) <=> T2(nan, 2, 3)) == std::partial_ordering::unordered);
+ assert((T1(1, nan, 3) <=> T2(1, nan, 3)) == std::partial_ordering::unordered);
+ assert((T1(1, 2, nan) <=> T2(1, 2, nan)) == std::partial_ordering::unordered);
+ }
+ // Ordering classes and synthesized three way comparison
+ {
+ typedef std::tuple<long, int, unsigned int> T1;
+ typedef std::tuple<int, long, unsigned short> T2;
+ // All strongly ordered members yields strong ordering.
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
+ }
+ {
+ struct WeakSpaceship {
+ constexpr bool operator==(const WeakSpaceship&) const { return true; }
+ constexpr std::weak_ordering operator<=>(const WeakSpaceship&) const { return std::weak_ordering::equivalent; }
+ };
+ {
+ typedef std::tuple<int, unsigned int, WeakSpaceship> T1;
+ typedef std::tuple<int, unsigned long, WeakSpaceship> T2;
+ // Strongly ordered members and a weakly ordered member yields weak ordering.
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
+ }
+ {
+ typedef std::tuple<unsigned int, int, WeakSpaceship> T1;
+ typedef std::tuple<double, long, WeakSpaceship> T2;
+ // Doubles are partially ordered, so one partial, one strong, and one weak ordering
+ // yields partial ordering.
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
+ }
+ }
+ {
+ struct NoSpaceship {
+ constexpr bool operator==(const NoSpaceship&) const { return true; }
+ constexpr bool operator<(const NoSpaceship&) const { return false; }
+ };
+ typedef std::tuple<int, unsigned int, NoSpaceship> T1;
+ typedef std::tuple<int, unsigned long, NoSpaceship> T2;
+ // Strongly ordered members and a weakly ordered member (synthesized) yields weak ordering.
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
+ }
+ {
+ struct SpaceshipNoEquals {
+ constexpr std::strong_ordering operator<=>(const SpaceshipNoEquals&) const { return std::strong_ordering::equal; }
+ constexpr bool operator<(const SpaceshipNoEquals&) const { return false; }
+ };
+ typedef std::tuple<int, unsigned int, SpaceshipNoEquals> T1;
+ typedef std::tuple<int, unsigned long, SpaceshipNoEquals> T2;
+ // Spaceship operator with no == operator falls back on the < operator and weak ordering.
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
+ }
+ {
+ struct CustomSpaceship {
+ constexpr CustomEquality operator<=>(const CustomSpaceship&) const { return CustomEquality(); }
+ };
+ typedef std::tuple<int, unsigned int, CustomSpaceship> T1;
+ typedef std::tuple<short, unsigned long, CustomSpaceship> T2;
+ typedef std::tuple<CustomSpaceship> T3;
+ // Custom three way return types cannot be used in synthesized three way comparison,
+ // but they can be used for (rewritten) operator< when synthesizing a weak ordering.
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
+ ASSERT_SAME_TYPE(decltype(T3() <=> T3()), std::weak_ordering);
+ }
+ {
+ typedef std::tuple<long, int> T1;
+ typedef std::tuple<long, unsigned int> T2;
+ // Even with the warning suppressed (-Wno-sign-compare) there should still be no <=> operator
+ // between signed and unsigned types, so we should end up with a synthesized weak ordering.
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
+ }
+
+#ifdef TEST_COMPILER_GCC
+ // GCC cannot evaluate NaN @ non-NaN constexpr, so test that runtime-only.
+ if (!std::is_constant_evaluated())
+#endif
+ {
+ {
+ typedef std::tuple<double> T1;
+ typedef std::tuple<int> T2;
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
+ assert((T1(nan) <=> T2(1)) == std::partial_ordering::unordered);
+ }
+ {
+ typedef std::tuple<double, double> T1;
+ typedef std::tuple<int, int> T2;
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
+ assert((T1(nan, 2) <=> T2(1, 2)) == std::partial_ordering::unordered);
+ assert((T1(1, nan) <=> T2(1, 2)) == std::partial_ordering::unordered);
+ }
+ {
+ typedef std::tuple<double, double, double> T1;
+ typedef std::tuple<int, int, int> T2;
+ constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+ ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
+ assert((T1(nan, 2, 3) <=> T2(1, 2, 3)) == std::partial_ordering::unordered);
+ assert((T1(1, nan, 3) <=> T2(1, 2, 3)) == std::partial_ordering::unordered);
+ assert((T1(1, 2, nan) <=> T2(1, 2, 3)) == std::partial_ordering::unordered);
+ }
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
More information about the libcxx-commits
mailing list