[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