Skip to content

WIP [libc++]P2944R3: Constrained comparisions - variant and tuple #141396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

H-G-Hristov
Copy link
Contributor

@H-G-Hristov H-G-Hristov commented May 25, 2025

This is a follow-up and depends on #139368 being merged first.

Implements P2944R3 partially, which adds constrained comparisons to std::variant and std::tuple.

The missing overloads introduced in P2165R4 are not implemented.

Relates to #136765
Closes #136769

Depends on #139368

References

tuple.rel
variant.relops

Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions ,cpp,h -- libcxx/include/tuple libcxx/include/variant libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp libcxx/test/std/utilities/variant/variant.relops/relops.pass.cpp libcxx/test/support/test_comparisons.h
View the diff from clang-format here.
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
index baca362f7..779a89b16 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
@@ -36,8 +36,10 @@ static_assert(!std::equality_comparable<std::tuple<NonComparable, EqualityCompar
 static_assert(!std::equality_comparable_with<std::tuple<EqualityComparable>, std::tuple<NonComparable>>);
 static_assert(!std::equality_comparable_with<std::tuple<NonComparable>, std::tuple<EqualityComparable>>);
 // Size mismatch.
-static_assert(!std::equality_comparable_with<std::tuple<EqualityComparable>, std::tuple<EqualityComparable, EqualityComparable>>);
-static_assert(!std::equality_comparable_with<std::tuple<EqualityComparable, EqualityComparable>, std::tuple<EqualityComparable>>);
+static_assert(
+    !std::equality_comparable_with<std::tuple<EqualityComparable>, std::tuple<EqualityComparable, EqualityComparable>>);
+static_assert(
+    !std::equality_comparable_with<std::tuple<EqualityComparable, EqualityComparable>, std::tuple<EqualityComparable>>);
 
 #endif
 

@Zingam Zingam linked an issue May 25, 2025 that may be closed by this pull request
@Zingam Zingam added libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. c++26 labels May 25, 2025
@llvmbot
Copy link
Member

llvmbot commented May 25, 2025

@llvm/pr-subscribers-libcxx

Author: Hristo Hristov (H-G-Hristov)

Changes

This is a follow-up and depends on #139368 being merged first.

Implements P2944R3 partially, which adds constrained comparisons to std::variant and std::tuple.

The missing overloads introduced in P2165R4 are not implemented.

Relates to #136765
Closes #136769

Relates to: #139368

References

tuple.rel
variant.relops


Full diff: https://github.com/llvm/llvm-project/pull/141396.diff

6 Files Affected:

  • (modified) libcxx/include/tuple (+7)
  • (modified) libcxx/include/variant (+31)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp (+21-2)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp (+6)
  • (modified) libcxx/test/std/utilities/variant/variant.relops/relops.pass.cpp (+49)
  • (modified) libcxx/test/support/test_comparisons.h (+65-1)
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 8dd62ae624f5e..35ea37a5ea5ed 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -1161,9 +1161,16 @@ struct __tuple_equal<0> {
 };
 
 template <class... _Tp, class... _Up>
+#    if _LIBCPP_STD_VER >= 26
+  requires(requires(const _Tp& __t, const _Up& __u) {
+            { __t == __u } -> __boolean_testable;
+          } && ...) && (sizeof...(_Tp) == sizeof...(_Up))
+#    endif
 inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool
 operator==(const tuple<_Tp...>& __x, const tuple<_Up...>& __y) {
+#    if _LIBCPP_STD_VER < 26
   static_assert(sizeof...(_Tp) == sizeof...(_Up), "Can't compare tuples of different sizes");
+#    endif
   return __tuple_equal<sizeof...(_Tp)>()(__x, __y);
 }
 
diff --git a/libcxx/include/variant b/libcxx/include/variant
index bf611aec704ca..f33058b708528 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -242,6 +242,7 @@ namespace std {
 #  include <__type_traits/is_assignable.h>
 #  include <__type_traits/is_constructible.h>
 #  include <__type_traits/is_convertible.h>
+#  include <__type_traits/is_core_convertible.h>
 #  include <__type_traits/is_destructible.h>
 #  include <__type_traits/is_nothrow_assignable.h>
 #  include <__type_traits/is_nothrow_constructible.h>
@@ -1453,6 +1454,11 @@ struct __convert_to_bool {
 };
 
 template <class... _Types>
+#    if _LIBCPP_STD_VER >= 26
+  requires(requires(const _Types& __t) {
+    { __t == __t } -> __core_convertible_to<bool>;
+  } && ...)
+#    endif
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const variant<_Types...>& __lhs, const variant<_Types...>& __rhs) {
   using __variant_detail::__visitation::__variant;
   if (__lhs.index() != __rhs.index())
@@ -1485,6 +1491,11 @@ operator<=>(const variant<_Types...>& __lhs, const variant<_Types...>& __rhs) {
 #    endif // _LIBCPP_STD_VER >= 20
 
 template <class... _Types>
+#    if _LIBCPP_STD_VER >= 26
+  requires(requires(const _Types& __t) {
+    { __t != __t } -> __core_convertible_to<bool>;
+  } && ...)
+#    endif
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const variant<_Types...>& __lhs, const variant<_Types...>& __rhs) {
   using __variant_detail::__visitation::__variant;
   if (__lhs.index() != __rhs.index())
@@ -1495,6 +1506,11 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const variant<_Types...>& __lhs,
 }
 
 template <class... _Types>
+#    if _LIBCPP_STD_VER >= 26
+  requires(requires(const _Types& __t) {
+    { __t < __t } -> __core_convertible_to<bool>;
+  } && ...)
+#    endif
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const variant<_Types...>& __lhs, const variant<_Types...>& __rhs) {
   using __variant_detail::__visitation::__variant;
   if (__rhs.valueless_by_exception())
@@ -1509,6 +1525,11 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const variant<_Types...>& __lhs,
 }
 
 template <class... _Types>
+#    if _LIBCPP_STD_VER >= 26
+  requires(requires(const _Types& __t) {
+    { __t > __t } -> __core_convertible_to<bool>;
+  } && ...)
+#    endif
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const variant<_Types...>& __lhs, const variant<_Types...>& __rhs) {
   using __variant_detail::__visitation::__variant;
   if (__lhs.valueless_by_exception())
@@ -1523,6 +1544,11 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const variant<_Types...>& __lhs,
 }
 
 template <class... _Types>
+#    if _LIBCPP_STD_VER >= 26
+  requires(requires(const _Types& __t) {
+    { __t <= __t } -> __core_convertible_to<bool>;
+  } && ...)
+#    endif
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const variant<_Types...>& __lhs, const variant<_Types...>& __rhs) {
   using __variant_detail::__visitation::__variant;
   if (__lhs.valueless_by_exception())
@@ -1537,6 +1563,11 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const variant<_Types...>& __lhs,
 }
 
 template <class... _Types>
+#    if _LIBCPP_STD_VER >= 26
+  requires(requires(const _Types& __t) {
+    { __t >= __t } -> __core_convertible_to<bool>;
+  } && ...)
+#    endif
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const variant<_Types...>& __lhs, const variant<_Types...>& __rhs) {
   using __variant_detail::__visitation::__variant;
   if (__rhs.valueless_by_exception())
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
index a8de656313d45..baca362f7783b 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
@@ -16,12 +16,31 @@
 
 // UNSUPPORTED: c++03
 
-#include <tuple>
-#include <string>
+#include <array>
 #include <cassert>
+#include <tuple>
 
+#include "test_comparisons.h"
 #include "test_macros.h"
 
+#if TEST_STD_VER >= 26
+
+// Test SFINAE.
+
+static_assert(std::equality_comparable<std::tuple<EqualityComparable>>);
+static_assert(std::equality_comparable<std::tuple<EqualityComparable, EqualityComparable>>);
+
+static_assert(!std::equality_comparable<std::tuple<NonComparable>>);
+static_assert(!std::equality_comparable<std::tuple<EqualityComparable, NonComparable>>);
+static_assert(!std::equality_comparable<std::tuple<NonComparable, EqualityComparable>>);
+static_assert(!std::equality_comparable_with<std::tuple<EqualityComparable>, std::tuple<NonComparable>>);
+static_assert(!std::equality_comparable_with<std::tuple<NonComparable>, std::tuple<EqualityComparable>>);
+// Size mismatch.
+static_assert(!std::equality_comparable_with<std::tuple<EqualityComparable>, std::tuple<EqualityComparable, EqualityComparable>>);
+static_assert(!std::equality_comparable_with<std::tuple<EqualityComparable, EqualityComparable>, std::tuple<EqualityComparable>>);
+
+#endif
+
 int main(int, char**)
 {
     {
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
index 851f6fcd1fbac..a5dbb6b313e6a 100644
--- 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
@@ -21,9 +21,15 @@
 
 #include <tuple>
 
+#include "test_macros.h"
+
+#if TEST_STD_VER >= 26
+// expected-no-diagnostics
+#else
 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@*:* {{}}
 }
+#endif
diff --git a/libcxx/test/std/utilities/variant/variant.relops/relops.pass.cpp b/libcxx/test/std/utilities/variant/variant.relops/relops.pass.cpp
index c1a5b8e474a74..2c00703662687 100644
--- a/libcxx/test/std/utilities/variant/variant.relops/relops.pass.cpp
+++ b/libcxx/test/std/utilities/variant/variant.relops/relops.pass.cpp
@@ -39,8 +39,57 @@
 #include <utility>
 #include <variant>
 
+#include "test_comparisons.h"
 #include "test_macros.h"
 
+#if TEST_STD_VER >= 26
+
+// Test SFINAE.
+
+// ==
+static_assert(HasOperatorEqual<std::variant<EqualityComparable>>);
+static_assert(HasOperatorEqual<std::variant<EqualityComparable, int, long>>);
+
+static_assert(!HasOperatorEqual<std::variant<NonComparable>>);
+static_assert(!HasOperatorEqual<std::variant<NonComparable, EqualityComparable>>);
+
+// >
+static_assert(HasOperatorGreaterThan<std::variant<ThreeWayComparable>>);
+static_assert(HasOperatorGreaterThan<std::variant<ThreeWayComparable, int, long>>);
+
+static_assert(!HasOperatorGreaterThan<std::variant<NonComparable>>);
+static_assert(!HasOperatorGreaterThan<std::variant<NonComparable, ThreeWayComparable>>);
+
+// >=
+static_assert(HasOperatorGreaterThanEqual<std::variant<ThreeWayComparable>>);
+static_assert(HasOperatorGreaterThanEqual<std::variant<ThreeWayComparable, int, long>>);
+
+static_assert(!HasOperatorGreaterThanEqual<std::variant<NonComparable>>);
+static_assert(!HasOperatorGreaterThanEqual<std::variant<NonComparable, ThreeWayComparable>>);
+
+// <
+static_assert(HasOperatorLessThan<std::variant<ThreeWayComparable>>);
+static_assert(HasOperatorLessThan<std::variant<ThreeWayComparable, int, long>>);
+
+static_assert(!HasOperatorLessThan<std::variant<NonComparable>>);
+static_assert(!HasOperatorLessThan<std::variant<NonComparable, ThreeWayComparable>>);
+
+// <=
+static_assert(HasOperatorLessThanEqual<std::variant<ThreeWayComparable>>);
+static_assert(HasOperatorLessThanEqual<std::variant<ThreeWayComparable, int, long>>);
+
+static_assert(!HasOperatorLessThanEqual<std::variant<NonComparable>>);
+static_assert(!HasOperatorLessThanEqual<std::variant<NonComparable, ThreeWayComparable>>);
+
+// !=
+static_assert(HasOperatorNotEqual<std::variant<EqualityComparable>>);
+static_assert(HasOperatorNotEqual<std::variant<EqualityComparable, int, long>>);
+
+static_assert(!HasOperatorNotEqual<std::variant<NonComparable>>);
+static_assert(!HasOperatorNotEqual<std::variant<NonComparable, EqualityComparable>>);
+
+#endif
+
 #ifndef TEST_HAS_NO_EXCEPTIONS
 struct MakeEmptyT {
   MakeEmptyT() = default;
diff --git a/libcxx/test/support/test_comparisons.h b/libcxx/test/support/test_comparisons.h
index db6977a96a2fe..e37ab44828c70 100644
--- a/libcxx/test/support/test_comparisons.h
+++ b/libcxx/test/support/test_comparisons.h
@@ -268,6 +268,70 @@ struct PartialOrder {
   }
 };
 
-#endif
+template <typename T1, typename T2 = T1>
+concept HasOperatorEqual = requires(T1 t1, T2 t2) { t1 == t2; };
+
+template <typename T1, typename T2 = T1>
+concept HasOperatorGreaterThan = requires(T1 t1, T2 t2) { t1 > t2; };
+
+template <typename T1, typename T2 = T1>
+concept HasOperatorGreaterThanEqual = requires(T1 t1, T2 t2) { t1 >= t2; };
+template <typename T1, typename T2 = T1>
+concept HasOperatorLessThan = requires(T1 t1, T2 t2) { t1 < t2; };
+
+template <typename T1, typename T2 = T1>
+concept HasOperatorLessThanEqual = requires(T1 t1, T2 t2) { t1 <= t2; };
+
+template <typename T1, typename T2 = T1>
+concept HasOperatorNotEqual = requires(T1 t1, T2 t2) { t1 != t2; };
+
+template <typename T1, typename T2 = T1>
+concept HasOperatorSpaceship = requires(T1 t1, T2 t2) { t1 <=> t2; };
+
+struct NonComparable {};
+static_assert(!std::equality_comparable<NonComparable>);
+static_assert(!HasOperatorEqual<NonComparable>);
+static_assert(!HasOperatorGreaterThan<NonComparable>);
+static_assert(!HasOperatorGreaterThanEqual<NonComparable>);
+static_assert(!HasOperatorLessThan<NonComparable>);
+static_assert(!HasOperatorLessThanEqual<NonComparable>);
+static_assert(!HasOperatorNotEqual<NonComparable>);
+static_assert(!HasOperatorSpaceship<NonComparable>);
+
+class EqualityComparable {
+public:
+  constexpr EqualityComparable(int value) : value_{value} {};
+
+  friend constexpr bool operator==(const EqualityComparable&, const EqualityComparable&) noexcept = default;
+
+private:
+  int value_;
+};
+static_assert(std::equality_comparable<EqualityComparable>);
+static_assert(HasOperatorEqual<EqualityComparable>);
+static_assert(HasOperatorNotEqual<EqualityComparable>);
+
+class ThreeWayComparable {
+public:
+  constexpr ThreeWayComparable(int value) : value_{value} {};
+
+  friend constexpr bool operator==(const ThreeWayComparable&, const ThreeWayComparable&) noexcept = default;
+  friend constexpr std::strong_ordering
+  operator<=>(const ThreeWayComparable&, const ThreeWayComparable&) noexcept = default;
+
+private:
+  int value_;
+};
+static_assert(std::equality_comparable<ThreeWayComparable>);
+static_assert(std::three_way_comparable<ThreeWayComparable>);
+static_assert(HasOperatorEqual<ThreeWayComparable>);
+static_assert(HasOperatorGreaterThan<ThreeWayComparable>);
+static_assert(HasOperatorGreaterThanEqual<ThreeWayComparable>);
+static_assert(HasOperatorLessThan<ThreeWayComparable>);
+static_assert(HasOperatorLessThanEqual<ThreeWayComparable>);
+static_assert(HasOperatorNotEqual<ThreeWayComparable>);
+static_assert(HasOperatorSpaceship<ThreeWayComparable>);
+
+#endif // TEST_STD_VER >= 20
 
 #endif // TEST_COMPARISONS_H

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++26 libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

P2944R3: Constrained equality - std::variant
3 participants