diff mbox series

[committed] libstdc++: Constrain equality ops for std::pair, std::tuple, std::variant

Message ID 20240507134318.3817304-1-jwakely@redhat.com
State New
Headers show
Series [committed] libstdc++: Constrain equality ops for std::pair, std::tuple, std::variant | expand

Commit Message

Jonathan Wakely May 7, 2024, 12:49 p.m. UTC
Tested x86_64-linux. Pushed to trunk.

-- >8 --

Implement the changes from P2944R3 which add constraints to the
comparison operators of std::pair, std::tuple, and std::variant.

The paper also changes std::optional, but we already constrain its
comparisons using SFINAE on the return type. However, we need some
additional constraints on the [optional.comp.with.t] operators that
compare an optional with a value. The paper doesn't say to do that, but
I think it's needed because otherwise when the comparison for two
optional objects fails its constraints, the two overloads that are
supposed to be for comparing to a non-optional become the best overload
candidates, but are ambiguous (and we don't even get as far as checking
the constraints for satisfaction). I reported LWG 4072 for this.

The paper does not change std::expected, but probably should have done.
I'll submit an LWG issue about that and implement it separately.

Also add [[nodiscard]] to all these comparison operators.

libstdc++-v3/ChangeLog:

	* include/bits/stl_pair.h (operator==): Add constraint.
	* include/bits/version.def (constrained_equality): Define.
	* include/bits/version.h: Regenerate.
	* include/std/optional: Define feature test macro.
	(__optional_rep_op_t): Use is_convertible_v instead of
	is_convertible.
	* include/std/tuple: Define feature test macro.
	(operator==, __tuple_cmp, operator<=>): Reimplement C++20
	comparisons using lambdas. Add constraints.
	* include/std/utility: Define feature test macro.
	* include/std/variant: Define feature test macro.
	(_VARIANT_RELATION_FUNCTION_TEMPLATE): Add constraints.
	(variant): Remove unnecessary friend declarations for comparison
	operators.
	* testsuite/20_util/optional/relops/constrained.cc: New test.
	* testsuite/20_util/pair/comparison_operators/constrained.cc:
	New test.
	* testsuite/20_util/tuple/comparison_operators/constrained.cc:
	New test.
	* testsuite/20_util/variant/relops/constrained.cc: New test.
	* testsuite/20_util/tuple/comparison_operators/overloaded.cc:
	Disable for C++20 and later.
	* testsuite/20_util/tuple/comparison_operators/overloaded2.cc:
	Remove dg-error line for target c++20.
---
 libstdc++-v3/include/bits/stl_pair.h          |  16 +-
 libstdc++-v3/include/bits/version.def         |   9 +
 libstdc++-v3/include/bits/version.h           |  10 +
 libstdc++-v3/include/std/optional             |  50 +++-
 libstdc++-v3/include/std/tuple                | 102 ++++---
 libstdc++-v3/include/std/utility              |   1 +
 libstdc++-v3/include/std/variant              |  28 +-
 .../20_util/optional/relops/constrained.cc    | 258 ++++++++++++++++++
 .../pair/comparison_operators/constrained.cc  |  48 ++++
 .../tuple/comparison_operators/constrained.cc |  50 ++++
 .../tuple/comparison_operators/overloaded.cc  |   6 +-
 .../tuple/comparison_operators/overloaded2.cc |   1 -
 .../20_util/variant/relops/constrained.cc     | 175 ++++++++++++
 13 files changed, 679 insertions(+), 75 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc
diff mbox series

Patch

diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
index 45317417c9c..0c1e5719a1a 100644
--- a/libstdc++-v3/include/bits/stl_pair.h
+++ b/libstdc++-v3/include/bits/stl_pair.h
@@ -1000,14 +1000,19 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _T1, typename _T2> pair(_T1, _T2) -> pair<_T1, _T2>;
 #endif
 
-#if __cpp_lib_three_way_comparison && __cpp_lib_concepts
+#if __cpp_lib_three_way_comparison
   // _GLIBCXX_RESOLVE_LIB_DEFECTS
   // 3865. Sorting a range of pairs
 
   /// Two pairs are equal iff their members are equal.
   template<typename _T1, typename _T2, typename _U1, typename _U2>
-    inline _GLIBCXX_CONSTEXPR bool
+    [[nodiscard]]
+    constexpr bool
     operator==(const pair<_T1, _T2>& __x, const pair<_U1, _U2>& __y)
+    requires requires {
+      { __x.first == __y.first } -> __detail::__boolean_testable;
+      { __x.second == __y.second } -> __detail::__boolean_testable;
+    }
     { return __x.first == __y.first && __x.second == __y.second; }
 
   /** Defines a lexicographical order for pairs.
@@ -1018,6 +1023,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
    * less than `Q.second`.
   */
   template<typename _T1, typename _T2, typename _U1, typename _U2>
+    [[nodiscard]]
     constexpr common_comparison_category_t<__detail::__synth3way_t<_T1, _U1>,
 					   __detail::__synth3way_t<_T2, _U2>>
     operator<=>(const pair<_T1, _T2>& __x, const pair<_U1, _U2>& __y)
@@ -1029,6 +1035,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #else
   /// Two pairs of the same type are equal iff their members are equal.
   template<typename _T1, typename _T2>
+    _GLIBCXX_NODISCARD
     inline _GLIBCXX_CONSTEXPR bool
     operator==(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
     { return __x.first == __y.first && __x.second == __y.second; }
@@ -1041,6 +1048,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
    * than `Q.second`.
   */
   template<typename _T1, typename _T2>
+    _GLIBCXX_NODISCARD
     inline _GLIBCXX_CONSTEXPR bool
     operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
     { return __x.first < __y.first
@@ -1048,24 +1056,28 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /// Uses @c operator== to find the result.
   template<typename _T1, typename _T2>
+    _GLIBCXX_NODISCARD
     inline _GLIBCXX_CONSTEXPR bool
     operator!=(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
     { return !(__x == __y); }
 
   /// Uses @c operator< to find the result.
   template<typename _T1, typename _T2>
+    _GLIBCXX_NODISCARD
     inline _GLIBCXX_CONSTEXPR bool
     operator>(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
     { return __y < __x; }
 
   /// Uses @c operator< to find the result.
   template<typename _T1, typename _T2>
+    _GLIBCXX_NODISCARD
     inline _GLIBCXX_CONSTEXPR bool
     operator<=(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
     { return !(__y < __x); }
 
   /// Uses @c operator< to find the result.
   template<typename _T1, typename _T2>
+    _GLIBCXX_NODISCARD
     inline _GLIBCXX_CONSTEXPR bool
     operator>=(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
     { return !(__x < __y); }
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 5c0477fb61e..f0ba4f2bb3d 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1237,6 +1237,15 @@  ftms = {
   };
 };
 
+ftms = {
+  name = constrained_equality;
+  values = {
+    v = 202403;
+    cxxmin = 20;
+    extra_cond = "__glibcxx_three_way_comparison";
+  };
+};
+
 ftms = {
   name = erase_if;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 65e708c73fb..f30f51dcedc 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1373,6 +1373,16 @@ 
 #endif /* !defined(__cpp_lib_constexpr_vector) && defined(__glibcxx_want_constexpr_vector) */
 #undef __glibcxx_want_constexpr_vector
 
+#if !defined(__cpp_lib_constrained_equality)
+# if (__cplusplus >= 202002L) && (__glibcxx_three_way_comparison)
+#  define __glibcxx_constrained_equality 202403L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_constrained_equality)
+#   define __cpp_lib_constrained_equality 202403L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_constrained_equality) && defined(__glibcxx_want_constrained_equality) */
+#undef __glibcxx_want_constrained_equality
+
 #if !defined(__cpp_lib_erase_if)
 # if (__cplusplus >= 202002L) && _GLIBCXX_HOSTED
 #  define __glibcxx_erase_if 202002L
diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index 3507c36a4d8..48e0f3d36f2 100644
--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -34,6 +34,7 @@ 
 
 #define __glibcxx_want_freestanding_optional
 #define __glibcxx_want_optional
+#define __glibcxx_want_constrained_equality
 #include <bits/version.h>
 
 #ifdef __cpp_lib_optional // C++ >= 17
@@ -1194,7 +1195,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   template<typename _Tp>
     using __optional_relop_t =
-      enable_if_t<is_convertible<_Tp, bool>::value, bool>;
+      enable_if_t<is_convertible_v<_Tp, bool>, bool>;
 
   template<typename _Tp, typename _Up>
     using __optional_eq_t = __optional_relop_t<
@@ -1279,6 +1280,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #ifdef __cpp_lib_three_way_comparison
   template<typename _Tp, three_way_comparable_with<_Tp> _Up>
+    [[nodiscard]]
     constexpr compare_three_way_result_t<_Tp, _Up>
     operator<=>(const optional<_Tp>& __x, const optional<_Up>& __y)
     {
@@ -1288,12 +1290,14 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   // Comparisons with nullopt.
   template<typename _Tp>
+    [[nodiscard]]
     constexpr bool
     operator==(const optional<_Tp>& __lhs, nullopt_t) noexcept
     { return !__lhs; }
 
 #ifdef __cpp_lib_three_way_comparison
   template<typename _Tp>
+    [[nodiscard]]
     constexpr strong_ordering
     operator<=>(const optional<_Tp>& __x, nullopt_t) noexcept
     { return bool(__x) <=> false; }
@@ -1354,76 +1358,96 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     { return !__rhs; }
 #endif // three-way-comparison
 
+  // _GLIBCXX_RESOLVE_LIB_DEFECTS
+  // 4072. std::optional comparisons: constrain harder
+#if __cpp_lib_concepts
+# define _REQUIRES_NOT_OPTIONAL(T) requires (!__is_optional_v<T>)
+#else
+# define _REQUIRES_NOT_OPTIONAL(T)
+#endif
+
   // Comparisons with value type.
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator==(const optional<_Tp>& __lhs, const _Up& __rhs)
+    operator== [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs)
     -> __optional_eq_t<_Tp, _Up>
     { return __lhs && *__lhs == __rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
     operator==(const _Up& __lhs, const optional<_Tp>& __rhs)
     -> __optional_eq_t<_Up, _Tp>
     { return __rhs && __lhs == *__rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator!=(const optional<_Tp>& __lhs, const _Up& __rhs)
+    operator!= [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs)
     -> __optional_ne_t<_Tp, _Up>
     { return !__lhs || *__lhs != __rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator!=(const _Up& __lhs, const optional<_Tp>& __rhs)
+    operator!= [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs)
     -> __optional_ne_t<_Up, _Tp>
     { return !__rhs || __lhs != *__rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator<(const optional<_Tp>& __lhs, const _Up& __rhs)
+    operator< [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs)
     -> __optional_lt_t<_Tp, _Up>
     { return !__lhs || *__lhs < __rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator<(const _Up& __lhs, const optional<_Tp>& __rhs)
+    operator< [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs)
     -> __optional_lt_t<_Up, _Tp>
     { return __rhs && __lhs < *__rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator>(const optional<_Tp>& __lhs, const _Up& __rhs)
+    operator> [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs)
     -> __optional_gt_t<_Tp, _Up>
     { return __lhs && *__lhs > __rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator>(const _Up& __lhs, const optional<_Tp>& __rhs)
+    operator> [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs)
     -> __optional_gt_t<_Up, _Tp>
     { return !__rhs || __lhs > *__rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator<=(const optional<_Tp>& __lhs, const _Up& __rhs)
+    operator<= [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs)
     -> __optional_le_t<_Tp, _Up>
     { return !__lhs || *__lhs <= __rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator<=(const _Up& __lhs, const optional<_Tp>& __rhs)
+    operator<= [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs)
     -> __optional_le_t<_Up, _Tp>
     { return __rhs && __lhs <= *__rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator>=(const optional<_Tp>& __lhs, const _Up& __rhs)
+    operator>= [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs)
     -> __optional_ge_t<_Tp, _Up>
     { return __lhs && *__lhs >= __rhs; }
 
   template<typename _Tp, typename _Up>
+    _REQUIRES_NOT_OPTIONAL(_Up)
     constexpr auto
-    operator>=(const _Up& __lhs, const optional<_Tp>& __rhs)
+    operator>= [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs)
     -> __optional_ge_t<_Up, _Tp>
     { return !__rhs || __lhs >= *__rhs; }
 
@@ -1432,7 +1456,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     requires (!__is_optional_v<_Up>)
       && three_way_comparable_with<_Up, _Tp>
     constexpr compare_three_way_result_t<_Tp, _Up>
-    operator<=>(const optional<_Tp>& __x, const _Up& __v)
+    operator<=> [[nodiscard]] (const optional<_Tp>& __x, const _Up& __v)
     { return bool(__x) ? *__x <=> __v : strong_ordering::less; }
 #endif
 
diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index 3065058e184..df3f6e38eeb 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -51,6 +51,7 @@ 
 #define __glibcxx_want_make_from_tuple
 #define __glibcxx_want_ranges_zip
 #define __glibcxx_want_tuple_like
+#define __glibcxx_want_constrained_equality
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -250,17 +251,11 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #if __cpp_lib_tuple_like // >= C++23
   struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
 
-  // These forward declarations are used by the operator<=> overload for
+  // This forward declaration is used by the operator<=> overload for
   // tuple-like types.
-  template<typename _Cat, typename _Tp, typename _Up>
+  template<typename _Cat, typename _Tp, typename _Up, typename _IndexSeq>
     constexpr _Cat
-    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
-
-  template<typename _Cat, typename _Tp, typename _Up,
-	   size_t _Idx0, size_t... _Idxs>
-    constexpr _Cat
-    __tuple_cmp(const _Tp& __t, const _Up& __u,
-		index_sequence<_Idx0, _Idxs...>);
+    __tuple_cmp(const _Tp& __t, const _Up& __u, _IndexSeq);
 #endif // C++23
 
   /**
@@ -1848,7 +1843,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<__tuple_like _UTuple>
 	requires (!__is_tuple_v<_UTuple>)
 	friend constexpr bool
-	operator==(const tuple& __t, const _UTuple& __u)
+	operator== [[nodiscard]] (const tuple& __t, const _UTuple& __u)
 	{
 	  static_assert(sizeof...(_Elements) == tuple_size_v<_UTuple>,
 	      "tuple objects can only be compared if they have equal sizes.");
@@ -2521,6 +2516,58 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 #endif
 
+#if __cpp_lib_three_way_comparison
+  template<typename... _Tps, typename... _Ups>
+    requires (sizeof...(_Tps) == sizeof...(_Ups))
+      && (requires (const _Tps& __t, const _Ups& __u) {
+	{ __t == __u } -> __detail::__boolean_testable;
+      } && ...)
+    constexpr bool
+    operator== [[nodiscard]] (const tuple<_Tps...>& __t,
+			      const tuple<_Ups...>& __u)
+    {
+      return [&]<size_t... _Inds>(index_sequence<_Inds...>) {
+	// Fold == over the tuples until non-equal elements are found.
+	return ((std::get<_Inds>(__t) == std::get<_Inds>(__u)) && ...);
+      }(index_sequence_for<_Tps...>{});
+    }
+
+  template<typename _Cat, typename _Tp, typename _Up, typename _IndexSeq>
+    [[nodiscard]]
+    constexpr _Cat
+    __tuple_cmp(const _Tp& __t, const _Up& __u, _IndexSeq __indices)
+    {
+      _Cat __c = _Cat::equivalent;
+
+      // Set __c to the comparison result of two corresponding elements.
+      // Return true they are equivalent.
+      auto __cmp = [&]<size_t _Ind>(integral_constant<size_t, _Ind>) {
+	__c = __detail::__synth3way(std::get<_Ind>(__t), std::get<_Ind>(__u));
+	return __c == 0;
+      };
+
+      [&]<size_t... _Inds>(index_sequence<_Inds...>) {
+	// Fold __cmp over the tuples until non-equivalent elements are found.
+	(void)(__cmp(integral_constant<size_t, _Inds>{}) && ...);
+      }(__indices);
+
+      return __c;
+    }
+
+  template<typename... _Tps, typename... _Ups>
+    requires (sizeof...(_Tps) == sizeof...(_Ups))
+      && (requires { typename __detail::__synth3way_t<_Tps, _Ups>; } && ...)
+    constexpr
+    common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...>
+    operator<=> [[nodiscard]] (const tuple<_Tps...>& __t,
+			       const tuple<_Ups...>& __u)
+    {
+      using _Cat
+	= common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...>;
+      return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Tps...>());
+    }
+#else
+
   // This class performs the comparison operations on tuples
   template<typename _Tp, typename _Up, size_t __i, size_t __size>
     struct __tuple_compare
@@ -2552,6 +2599,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     };
 
   template<typename... _TElements, typename... _UElements>
+    _GLIBCXX_NODISCARD
     constexpr bool
     operator==(const tuple<_TElements...>& __t,
 	       const tuple<_UElements...>& __u)
@@ -2564,36 +2612,8 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       return __compare::__eq(__t, __u);
     }
 
-#if __cpp_lib_three_way_comparison
-  template<typename _Cat, typename _Tp, typename _Up>
-    constexpr _Cat
-    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>)
-    { return _Cat::equivalent; }
-
-  template<typename _Cat, typename _Tp, typename _Up,
-	   size_t _Idx0, size_t... _Idxs>
-    constexpr _Cat
-    __tuple_cmp(const _Tp& __t, const _Up& __u,
-		index_sequence<_Idx0, _Idxs...>)
-    {
-      auto __c
-	= __detail::__synth3way(std::get<_Idx0>(__t), std::get<_Idx0>(__u));
-      if (__c != 0)
-	return __c;
-      return std::__tuple_cmp<_Cat>(__t, __u, index_sequence<_Idxs...>());
-    }
-
-  template<typename... _Tps, typename... _Ups>
-    constexpr
-    common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...>
-    operator<=>(const tuple<_Tps...>& __t, const tuple<_Ups...>& __u)
-    {
-      using _Cat
-	= common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...>;
-      return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Tps...>());
-    }
-#else
   template<typename... _TElements, typename... _UElements>
+    _GLIBCXX_NODISCARD
     constexpr bool
     operator<(const tuple<_TElements...>& __t,
 	      const tuple<_UElements...>& __u)
@@ -2607,24 +2627,28 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 
   template<typename... _TElements, typename... _UElements>
+    _GLIBCXX_NODISCARD
     constexpr bool
     operator!=(const tuple<_TElements...>& __t,
 	       const tuple<_UElements...>& __u)
     { return !(__t == __u); }
 
   template<typename... _TElements, typename... _UElements>
+    _GLIBCXX_NODISCARD
     constexpr bool
     operator>(const tuple<_TElements...>& __t,
 	      const tuple<_UElements...>& __u)
     { return __u < __t; }
 
   template<typename... _TElements, typename... _UElements>
+    _GLIBCXX_NODISCARD
     constexpr bool
     operator<=(const tuple<_TElements...>& __t,
 	       const tuple<_UElements...>& __u)
     { return !(__u < __t); }
 
   template<typename... _TElements, typename... _UElements>
+    _GLIBCXX_NODISCARD
     constexpr bool
     operator>=(const tuple<_TElements...>& __t,
 	       const tuple<_UElements...>& __u)
diff --git a/libstdc++-v3/include/std/utility b/libstdc++-v3/include/std/utility
index 212513f6f48..56467160a2f 100644
--- a/libstdc++-v3/include/std/utility
+++ b/libstdc++-v3/include/std/utility
@@ -93,6 +93,7 @@ 
 #define __glibcxx_want_tuples_by_type
 #define __glibcxx_want_unreachable
 #define __glibcxx_want_tuple_like
+#define __glibcxx_want_constrained_equality
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
diff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant
index 748e9bae1cb..8072e1f17a8 100644
--- a/libstdc++-v3/include/std/variant
+++ b/libstdc++-v3/include/std/variant
@@ -33,6 +33,7 @@ 
 
 #define __glibcxx_want_freestanding_variant
 #define __glibcxx_want_variant
+#define __glibcxx_want_constrained_equality
 #include <bits/version.h>
 
 #ifdef __cpp_lib_variant // C++ >= 17
@@ -1236,9 +1237,19 @@  namespace __variant
 
   struct monostate { };
 
+#if __cpp_lib_concepts
+# define _VARIANT_RELATION_FUNCTION_CONSTRAINTS(TYPES, OP) \
+  requires ((requires (const TYPES& __t) { \
+	{ __t OP __t } -> __detail::__boolean_testable; }) && ...)
+#else
+# define _VARIANT_RELATION_FUNCTION_CONSTRAINTS(TYPES, OP)
+#endif
+
 #define _VARIANT_RELATION_FUNCTION_TEMPLATE(__OP, __NAME) \
   template<typename... _Types> \
-    constexpr bool operator __OP(const variant<_Types...>& __lhs, \
+    _VARIANT_RELATION_FUNCTION_CONSTRAINTS(_Types, __OP) \
+    constexpr bool \
+    operator __OP [[nodiscard]] (const variant<_Types...>& __lhs, \
 				 const variant<_Types...>& __rhs) \
     { \
       bool __ret = true; \
@@ -1690,21 +1701,6 @@  namespace __variant
       template<size_t _Np, typename _Vp>
 	friend constexpr decltype(auto)
 	__detail::__variant::__get(_Vp&& __v) noexcept;
-
-#define _VARIANT_RELATION_FUNCTION_TEMPLATE(__OP) \
-      template<typename... _Tp> \
-	friend constexpr bool \
-	operator __OP(const variant<_Tp...>& __lhs, \
-		      const variant<_Tp...>& __rhs);
-
-      _VARIANT_RELATION_FUNCTION_TEMPLATE(<)
-      _VARIANT_RELATION_FUNCTION_TEMPLATE(<=)
-      _VARIANT_RELATION_FUNCTION_TEMPLATE(==)
-      _VARIANT_RELATION_FUNCTION_TEMPLATE(!=)
-      _VARIANT_RELATION_FUNCTION_TEMPLATE(>=)
-      _VARIANT_RELATION_FUNCTION_TEMPLATE(>)
-
-#undef _VARIANT_RELATION_FUNCTION_TEMPLATE
     };
 
   template<size_t _Np, typename... _Types>
diff --git a/libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc b/libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc
new file mode 100644
index 00000000000..0e325618008
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc
@@ -0,0 +1,258 @@ 
+// { dg-do compile { target c++20 } }
+
+#include <optional>
+
+#ifndef __cpp_lib_constrained_equality
+# error "Feature-test macro for constrained_equality missing"
+#elif __cpp_lib_constrained_equality != 202403
+# error "Feature-test macro for constrained_equality has wrong value"
+#endif
+
+template<typename T, typename U = T>
+concept eq_comparable
+= requires (const std::optional<T>& t, const std::optional<U>& u) {
+  t == u;
+  *t == u;
+  t == *u;
+};
+
+template<typename T, typename U = T>
+concept ne_comparable
+= requires (const std::optional<T>& t, const std::optional<U>& u) {
+  t != u;
+  *t != u;
+  t != *u;
+};
+
+template<typename T, typename U = T>
+concept lt_comparable
+= requires (const std::optional<T>& t, const std::optional<U>& u) {
+  t < u;
+  *t < u;
+  t < *u;
+};
+
+template<typename T, typename U = T>
+concept le_comparable
+= requires (const std::optional<T>& t, const std::optional<U>& u) {
+  t <= u;
+  *t <= u;
+  t <= *u;
+};
+
+template<typename T, typename U = T>
+concept gt_comparable
+= requires (const std::optional<T>& t, const std::optional<U>& u) {
+  t > u;
+  *t > u;
+  t > *u;
+};
+
+template<typename T, typename U = T>
+concept ge_comparable
+= requires (const std::optional<T>& t, const std::optional<U>& u) {
+  t >= u;
+  *t >= u;
+  t >= *u;
+};
+
+static_assert( eq_comparable<int> );
+static_assert( ne_comparable<int> );
+static_assert( lt_comparable<int> );
+static_assert( le_comparable<int> );
+static_assert( gt_comparable<int> );
+static_assert( ge_comparable<int> );
+static_assert( eq_comparable<int, long> );
+static_assert( ne_comparable<int, long> );
+static_assert( lt_comparable<int, long> );
+static_assert( le_comparable<int, long> );
+static_assert( gt_comparable<int, long> );
+static_assert( ge_comparable<int, long> );
+static_assert( eq_comparable<short, int> );
+static_assert( ne_comparable<short, int> );
+static_assert( lt_comparable<short, int> );
+static_assert( le_comparable<short, int> );
+static_assert( gt_comparable<short, int> );
+static_assert( ge_comparable<short, int> );
+
+struct A { };
+static_assert( ! eq_comparable<A> );
+static_assert( ! ne_comparable<A> );
+static_assert( ! lt_comparable<A> );
+static_assert( ! le_comparable<A> );
+static_assert( ! gt_comparable<A> );
+static_assert( ! ge_comparable<A> );
+static_assert( ! eq_comparable<A, A> );
+static_assert( ! ne_comparable<A, A> );
+static_assert( ! lt_comparable<A, A> );
+static_assert( ! le_comparable<A, A> );
+static_assert( ! gt_comparable<A, A> );
+static_assert( ! ge_comparable<A, A> );
+static_assert( ! eq_comparable<A, int> );
+static_assert( ! ne_comparable<A, int> );
+static_assert( ! lt_comparable<A, int> );
+static_assert( ! le_comparable<A, int> );
+static_assert( ! gt_comparable<A, int> );
+static_assert( ! ge_comparable<A, int> );
+
+struct B { };
+void operator==(B, B);
+static_assert( ! eq_comparable<B> );
+static_assert( ! ne_comparable<B> );
+static_assert( ! lt_comparable<B> );
+static_assert( ! le_comparable<B> );
+static_assert( ! gt_comparable<B> );
+static_assert( ! ge_comparable<B> );
+static_assert( ! eq_comparable<B, B> );
+static_assert( ! ne_comparable<B, B> );
+static_assert( ! lt_comparable<B, B> );
+static_assert( ! le_comparable<B, B> );
+static_assert( ! gt_comparable<B, B> );
+static_assert( ! ge_comparable<B, B> );
+static_assert( ! eq_comparable<B, int> );
+static_assert( ! ne_comparable<B, int> );
+static_assert( ! lt_comparable<B, int> );
+static_assert( ! le_comparable<B, int> );
+static_assert( ! gt_comparable<B, int> );
+static_assert( ! ge_comparable<B, int> );
+
+struct C { };
+bool operator==(C, C);
+static_assert( eq_comparable<C> );
+static_assert( ne_comparable<C> );
+static_assert( ! lt_comparable<C> );
+static_assert( ! le_comparable<C> );
+static_assert( ! gt_comparable<C> );
+static_assert( ! ge_comparable<C> );
+static_assert( eq_comparable<C, C> );
+static_assert( ne_comparable<C, C> );
+static_assert( eq_comparable<C, C> );
+static_assert( ne_comparable<C, C> );
+
+struct D { };
+int operator==(D, D);
+bool operator!=(D, D) = delete;
+static_assert( eq_comparable<D> );
+static_assert( ! ne_comparable<D> );
+static_assert( ! lt_comparable<D> );
+static_assert( ! le_comparable<D> );
+static_assert( ! gt_comparable<D> );
+static_assert( ! ge_comparable<D> );
+static_assert( eq_comparable<D, D> );
+static_assert( ! ne_comparable<D, D> );
+static_assert( eq_comparable<D, D> );
+static_assert( ! ne_comparable<D, D> );
+
+struct E { };
+bool operator==(/* not-const */ E&, const E&);
+static_assert( ! eq_comparable<E> );
+static_assert( ! ne_comparable<E> );
+static_assert( ! lt_comparable<E> );
+static_assert( ! le_comparable<E> );
+static_assert( ! gt_comparable<E> );
+static_assert( ! ge_comparable<E> );
+static_assert( ! eq_comparable<E, E> );
+static_assert( ! eq_comparable<E, E> );
+
+struct F { };
+bool operator<(F, F);
+void operator>(F, F);
+static_assert( ! eq_comparable<F> );
+static_assert( ! ne_comparable<F> );
+static_assert( lt_comparable<F> );
+static_assert( ! le_comparable<F> );
+static_assert( ! gt_comparable<F> );
+static_assert( ! ge_comparable<F> );
+static_assert( lt_comparable<F, F> );
+static_assert( lt_comparable<F, F> );
+
+struct G { };
+bool operator<=(G, G);
+void operator<(G, G);
+static_assert( ! eq_comparable<G> );
+static_assert( ! ne_comparable<G> );
+static_assert( ! lt_comparable<G> );
+static_assert( le_comparable<G> );
+static_assert( ! gt_comparable<G> );
+static_assert( ! ge_comparable<G> );
+static_assert( le_comparable<G, G> );
+static_assert( le_comparable<G, G> );
+
+struct H { };
+bool operator>(H, H);
+void operator>=(H, H);
+static_assert( ! eq_comparable<H> );
+static_assert( ! ne_comparable<H> );
+static_assert( ! lt_comparable<H> );
+static_assert( ! le_comparable<H> );
+static_assert( gt_comparable<H> );
+static_assert( ! ge_comparable<H> );
+static_assert( gt_comparable<H, H> );
+static_assert( gt_comparable<H, H> );
+
+struct I { };
+bool operator>=(I, I);
+void operator<=(I, I);
+static_assert( ! eq_comparable<I> );
+static_assert( ! ne_comparable<I> );
+static_assert( ! lt_comparable<I> );
+static_assert( ! le_comparable<I> );
+static_assert( ! gt_comparable<I> );
+static_assert( ge_comparable<I> );
+static_assert( ge_comparable<I, I> );
+static_assert( ge_comparable<I, I> );
+
+struct J { };
+bool operator==(J, J);
+std::weak_ordering operator<=>(J, J);
+static_assert( eq_comparable<J> );
+static_assert( ne_comparable<J> );
+static_assert( lt_comparable<J> );
+static_assert( le_comparable<J> );
+static_assert( gt_comparable<J> );
+static_assert( ge_comparable<J> );
+
+struct K { };
+int operator==(K, K); // non-bool prevents synthesis of !=
+void operator<=(K, K);
+std::weak_ordering operator<=>(K, K);
+static_assert( eq_comparable<K> );
+static_assert( ! ne_comparable<K> );
+static_assert( lt_comparable<K> );
+static_assert( ! le_comparable<K> );
+static_assert( gt_comparable<K> );
+static_assert( ge_comparable<K> );
+
+bool operator==(A, B);
+static_assert( eq_comparable<A, B> );
+static_assert( eq_comparable<B, A> );
+static_assert( ne_comparable<A, B> );
+static_assert( ne_comparable<B, A> );
+static_assert( ! lt_comparable<A, B> );
+static_assert( ! le_comparable<A, B> );
+static_assert( ! gt_comparable<A, B> );
+static_assert( ! ge_comparable<A, B> );
+
+int operator==(C, D); // non-bool prevents synthesis of != and reversed args
+static_assert( eq_comparable<C, D> );
+static_assert( ! eq_comparable<D, C> );
+static_assert( ! ne_comparable<C, D> );
+static_assert( ! ne_comparable<D, C> );
+static_assert( ! lt_comparable<C, D> );
+static_assert( ! le_comparable<C, D> );
+static_assert( ! gt_comparable<C, D> );
+static_assert( ! ge_comparable<C, D> );
+
+std::weak_ordering operator<=>(E, F);
+static_assert( ! eq_comparable<E, F> );
+static_assert( ! eq_comparable<F, E> );
+static_assert( ! ne_comparable<E, F> );
+static_assert( ! ne_comparable<F, E> );
+static_assert( lt_comparable<E, F> );
+static_assert( le_comparable<E, F> );
+static_assert( gt_comparable<E, F> );
+static_assert( ge_comparable<E, F> );
+static_assert( lt_comparable<F, E> );
+static_assert( le_comparable<F, E> );
+static_assert( gt_comparable<F, E> );
+static_assert( ge_comparable<F, E> );
diff --git a/libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc b/libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc
new file mode 100644
index 00000000000..a35dbd265a7
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc
@@ -0,0 +1,48 @@ 
+// { dg-do compile { target c++20 } }
+
+#include <utility>
+
+#ifndef __cpp_lib_constrained_equality
+# error "Feature-test macro for constrained_equality missing"
+#elif __cpp_lib_constrained_equality != 202403
+# error "Feature-test macro for constrained_equality has wrong value"
+#endif
+
+template<typename T>
+concept equality_comparable = requires (const T& t) { t == t; t != t; };
+
+static_assert( equality_comparable<std::pair<int, long>> );
+
+struct A { };
+static_assert( ! equality_comparable<std::pair<A, long>> );
+static_assert( ! equality_comparable<std::pair<int, A>> );
+static_assert( ! equality_comparable<std::pair<A, A>> );
+
+struct B { };
+void operator==(B, B);
+static_assert( ! equality_comparable<std::pair<B, long>> );
+static_assert( ! equality_comparable<std::pair<int, B>> );
+static_assert( ! equality_comparable<std::pair<B, B>> );
+static_assert( ! equality_comparable<std::pair<A, B>> );
+
+struct C { };
+int operator==(C, C);
+static_assert( equality_comparable<std::pair<C, long>> );
+static_assert( equality_comparable<std::pair<int, C>> );
+static_assert( equality_comparable<std::pair<C, C>> );
+static_assert( ! equality_comparable<std::pair<A, C>> );
+
+struct D { };
+bool operator==(D, D);
+bool operator!=(D, D) = delete;
+static_assert( equality_comparable<std::pair<D, long>> );
+static_assert( equality_comparable<std::pair<int, D>> );
+static_assert( equality_comparable<std::pair<D, D>> );
+static_assert( equality_comparable<std::pair<C, D>> );
+static_assert( ! equality_comparable<std::pair<A, C>> );
+
+struct E { };
+bool operator==(/* not-const */ E&, const E&);
+static_assert( ! equality_comparable<std::pair<E, long>> );
+static_assert( ! equality_comparable<std::pair<int, E>> );
+static_assert( ! equality_comparable<std::pair<E, E>> );
diff --git a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc
new file mode 100644
index 00000000000..47035ab18ba
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc
@@ -0,0 +1,50 @@ 
+// { dg-do compile { target c++20 } }
+
+#include <tuple>
+
+#ifndef __cpp_lib_constrained_equality
+# error "Feature-test macro for constrained_equality missing"
+#elif __cpp_lib_constrained_equality != 202403
+# error "Feature-test macro for constrained_equality has wrong value"
+#endif
+
+template<typename T>
+concept equality_comparable = requires (const T& t) { t == t; t != t; };
+
+static_assert( equality_comparable<std::tuple<>> );
+static_assert( equality_comparable<std::tuple<int>> );
+static_assert( equality_comparable<std::tuple<int, long>> );
+static_assert( equality_comparable<std::tuple<int, long, short>> );
+
+struct A { };
+static_assert( ! equality_comparable<std::tuple<A>> );
+static_assert( ! equality_comparable<std::tuple<int, A>> );
+static_assert( ! equality_comparable<std::tuple<A, A>> );
+
+struct B { };
+void operator==(B, B);
+static_assert( ! equality_comparable<std::tuple<B>> );
+static_assert( ! equality_comparable<std::tuple<int, B>> );
+static_assert( ! equality_comparable<std::tuple<B, B>> );
+
+struct C { };
+int operator==(C, C);
+static_assert( equality_comparable<std::tuple<C>> );
+static_assert( equality_comparable<std::tuple<int, C>> );
+static_assert( equality_comparable<std::tuple<C, C>> );
+static_assert( ! equality_comparable<std::tuple<A, C>> );
+
+struct D { };
+bool operator==(D, D);
+bool operator!=(D, D) = delete;
+static_assert( equality_comparable<std::tuple<D>> );
+static_assert( equality_comparable<std::tuple<int, D>> );
+static_assert( equality_comparable<std::tuple<D, D>> );
+static_assert( equality_comparable<std::tuple<C, D>> );
+static_assert( ! equality_comparable<std::tuple<A, C>> );
+
+struct E { };
+bool operator==(/* not-const */ E&, const E&);
+static_assert( ! equality_comparable<std::tuple<E>> );
+static_assert( ! equality_comparable<std::tuple<int, E>> );
+static_assert( ! equality_comparable<std::tuple<E, E>> );
diff --git a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc
index 9f8a0f91785..7ae7f42d1a1 100644
--- a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc
+++ b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc
@@ -1,4 +1,5 @@ 
-// { dg-do compile { target c++11 } }
+// { dg-do compile { target { c++11 && c++17_down } } }
+// Not valid in C++20, because TwistedLogic doesn't model boolean-testable.
 
 // Copyright (C) 2014-2024 Free Software Foundation, Inc.
 //
@@ -49,8 +50,5 @@  TwistedLogic operator<(const Compares&, const Compares&) { return {false}; }
 auto a = std::make_tuple(nullptr, Compares{}, 2, 'U');
 auto b = a == a;
 
-#if ! __cpp_lib_three_way_comparison
-// Not valid in C++20, because TwistedLogic doesn't model boolean-testable.
 auto c = std::make_tuple("", Compares{}, 2, 'U');
 auto d = c < c;
-#endif
diff --git a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc
index 19617a5676e..8ea7ed0b797 100644
--- a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc
+++ b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc
@@ -50,5 +50,4 @@  auto a = std::make_tuple(nullptr, Compares{}, 2, 'U');
 auto b = a < a;
 
 // { dg-error "no match for 'operator<'" "" { target c++20 } 0 }
-// { dg-error "no match for .*_Synth3way|in requirements" "" { target c++20 } 0 }
 // { dg-error "ordered comparison" "" { target c++17_down } 0 }
diff --git a/libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc b/libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc
new file mode 100644
index 00000000000..95e8f754d1e
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc
@@ -0,0 +1,175 @@ 
+// { dg-do compile { target c++20 } }
+
+#include <variant>
+
+#ifndef __cpp_lib_constrained_equality
+# error "Feature-test macro for constrained_equality missing"
+#elif __cpp_lib_constrained_equality != 202403
+# error "Feature-test macro for constrained_equality has wrong value"
+#endif
+
+template<typename T, typename... U>
+concept eq_comparable
+= requires (const std::variant<int, T, U...>& t) { t == t; };
+
+template<typename T, typename... U>
+concept ne_comparable
+= requires (const std::variant<int, T, U...>& t) { t != t; };
+
+template<typename T, typename... U>
+concept lt_comparable
+= requires (const std::variant<int, T, U...>& t) { t < t; };
+
+template<typename T, typename... U>
+concept le_comparable
+= requires (const std::variant<int, T, U...>& t) { t <= t; };
+
+template<typename T, typename... U>
+concept gt_comparable
+= requires (const std::variant<int, T, U...>& t) { t > t; };
+
+template<typename T, typename... U>
+concept ge_comparable
+= requires (const std::variant<int, T, U...>& t) { t >= t; };
+
+static_assert( eq_comparable<int> );
+static_assert( ne_comparable<int> );
+static_assert( lt_comparable<int> );
+static_assert( le_comparable<int> );
+static_assert( gt_comparable<int> );
+static_assert( ge_comparable<int> );
+static_assert( eq_comparable<int, long> );
+static_assert( ne_comparable<int, long> );
+static_assert( lt_comparable<int, long> );
+static_assert( le_comparable<int, long> );
+static_assert( gt_comparable<int, long> );
+static_assert( ge_comparable<int, long> );
+static_assert( eq_comparable<short, int> );
+static_assert( ne_comparable<short, int> );
+static_assert( lt_comparable<short, int> );
+static_assert( le_comparable<short, int> );
+static_assert( gt_comparable<short, int> );
+static_assert( ge_comparable<short, int> );
+static_assert( eq_comparable<std::monostate, char, int> );
+static_assert( ne_comparable<std::monostate, char, int> );
+static_assert( lt_comparable<std::monostate, char, int> );
+static_assert( le_comparable<std::monostate, char, int> );
+static_assert( gt_comparable<std::monostate, char, int> );
+static_assert( ge_comparable<std::monostate, char, int> );
+
+struct A { };
+static_assert( ! eq_comparable<A> );
+static_assert( ! ne_comparable<A> );
+static_assert( ! lt_comparable<A> );
+static_assert( ! le_comparable<A> );
+static_assert( ! gt_comparable<A> );
+static_assert( ! ge_comparable<A> );
+
+struct B { };
+void operator==(B, B);
+static_assert( ! eq_comparable<B> );
+static_assert( ! ne_comparable<B> );
+static_assert( ! lt_comparable<B> );
+static_assert( ! le_comparable<B> );
+static_assert( ! gt_comparable<B> );
+static_assert( ! ge_comparable<B> );
+
+struct C { };
+bool operator==(C, C);
+static_assert( eq_comparable<C> );
+static_assert( ne_comparable<C> );
+static_assert( ! lt_comparable<C> );
+static_assert( ! le_comparable<C> );
+static_assert( ! gt_comparable<C> );
+static_assert( ! ge_comparable<C> );
+static_assert( ne_comparable<C, C> );
+
+struct D { };
+int operator==(D, D); // variant's operator== returns bool despite int here
+bool operator!=(D, D) = delete;
+static_assert( eq_comparable<D> );
+static_assert( ne_comparable<D> ); // variant's operator== can be used
+static_assert( ! lt_comparable<D> );
+static_assert( ! le_comparable<D> );
+static_assert( ! gt_comparable<D> );
+static_assert( ! ge_comparable<D> );
+
+struct E { };
+bool operator==(/* not-const */ E&, const E&);
+static_assert( ! eq_comparable<E> );
+static_assert( ! ne_comparable<E> );
+static_assert( ! lt_comparable<E> );
+static_assert( ! le_comparable<E> );
+static_assert( ! gt_comparable<E> );
+static_assert( ! ge_comparable<E> );
+
+struct F { };
+bool operator<(F, F);
+void operator>(F, F);
+static_assert( ! eq_comparable<F> );
+static_assert( ! ne_comparable<F> );
+static_assert( lt_comparable<F> );
+static_assert( ! le_comparable<F> );
+static_assert( ! gt_comparable<F> );
+static_assert( ! ge_comparable<F> );
+
+struct G { };
+bool operator<=(G, G);
+void operator<(G, G);
+static_assert( ! eq_comparable<G> );
+static_assert( ! ne_comparable<G> );
+static_assert( ! lt_comparable<G> );
+static_assert( le_comparable<G> );
+static_assert( ! gt_comparable<G> );
+static_assert( ! ge_comparable<G> );
+
+struct H { };
+bool operator>(H, H);
+void operator>=(H, H);
+static_assert( ! eq_comparable<H> );
+static_assert( ! ne_comparable<H> );
+static_assert( ! lt_comparable<H> );
+static_assert( ! le_comparable<H> );
+static_assert( gt_comparable<H> );
+static_assert( ! ge_comparable<H> );
+static_assert( gt_comparable<H, H> );
+static_assert( gt_comparable<H, H> );
+
+struct I { };
+bool operator>=(I, I);
+void operator<=(I, I);
+static_assert( ! eq_comparable<I> );
+static_assert( ! ne_comparable<I> );
+static_assert( ! lt_comparable<I> );
+static_assert( ! le_comparable<I> );
+static_assert( ! gt_comparable<I> );
+static_assert( ge_comparable<I> );
+static_assert( ge_comparable<I, I> );
+static_assert( ge_comparable<I, I> );
+
+struct J { };
+bool operator==(J, J);
+std::weak_ordering operator<=>(J, J);
+static_assert( eq_comparable<J> );
+static_assert( ne_comparable<J> );
+static_assert( lt_comparable<J> );
+static_assert( le_comparable<J> );
+static_assert( gt_comparable<J> );
+static_assert( ge_comparable<J> );
+
+struct K { };
+int operator==(K, K); // variant's operator== returns bool despite int here
+void operator<=(K, K);
+std::weak_ordering operator<=>(K, K);
+static_assert( eq_comparable<K> );
+static_assert( ne_comparable<K> ); // variant's operator== can be used
+static_assert( lt_comparable<K> );
+static_assert( ! le_comparable<K> );
+static_assert( gt_comparable<K> );
+static_assert( ge_comparable<K> );
+static_assert( eq_comparable<K, J> );
+static_assert( ne_comparable<K, J> );
+static_assert( lt_comparable<K, J> );
+static_assert( ! le_comparable<K, J> );
+static_assert( gt_comparable<K, J> );
+static_assert( ge_comparable<K, J> );