[libcxx] [llvm] [libc++] Implement P3379R0 Constrain `std::expected` equality operators (PR #135759)

via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 22 07:35:26 PDT 2025


https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/135759

>From f32488c9f26758b78c055a70044bc18bc61e1673 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 15 Apr 2025 15:47:57 +0800
Subject: [PATCH 1/2] [libc++] Implement P3379R0 Constrain std::expected
 equality operators

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 libcxx/docs/Status/Cxx2cPapers.csv            |  2 +-
 libcxx/include/__expected/expected.h          | 33 +++++++++++++++----
 .../equality/equality.T2.pass.cpp             | 15 ++++-----
 .../equality/equality.other_expected.pass.cpp | 16 +++++----
 .../equality/equality.unexpected.pass.cpp     | 15 ++++-----
 .../equality/equality.other_expected.pass.cpp | 14 ++++----
 .../equality/equality.unexpected.pass.cpp     | 15 ++++-----
 libcxx/test/std/utilities/expected/types.h    | 13 ++++++++
 8 files changed, 79 insertions(+), 44 deletions(-)

diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index b40d9eb58c24e..be591c9b6a587 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -78,7 +78,7 @@
 "","","","","",""
 "`P3136R1 <https://wg21.link/P3136R1>`__","Retiring niebloids","2024-11 (Wrocław)","","",""
 "`P3138R5 <https://wg21.link/P3138R5>`__","``views::cache_latest``","2024-11 (Wrocław)","","",""
-"`P3379R0 <https://wg21.link/P3379R0>`__","Constrain ``std::expected`` equality operators","2024-11 (Wrocław)","","",""
+"`P3379R0 <https://wg21.link/P3379R0>`__","Constrain ``std::expected`` equality operators","2024-11 (Wrocław)","|Complete|","21",""
 "`P2862R1 <https://wg21.link/P2862R1>`__","``text_encoding::name()`` should never return null values","2024-11 (Wrocław)","","",""
 "`P2897R7 <https://wg21.link/P2897R7>`__","``aligned_accessor``: An ``mdspan`` accessor expressing pointer over-alignment","2024-11 (Wrocław)","","",""
 "`P3355R1 <https://wg21.link/P3355R1>`__","Fix ``submdspan`` for C++26","2024-11 (Wrocław)","","",""
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 03bbd1623ed5c..f8af6429c05ae 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -10,6 +10,7 @@
 #define _LIBCPP___EXPECTED_EXPECTED_H
 
 #include <__assert>
+#include <__concepts/boolean_testable.h>
 #include <__config>
 #include <__expected/bad_expected_access.h>
 #include <__expected/unexpect.h>
@@ -1139,8 +1140,12 @@ class expected : private __expected_base<_Tp, _Err> {
 
   // [expected.object.eq], equality operators
   template <class _T2, class _E2>
-    requires(!is_void_v<_T2>)
-  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
+    requires(!is_void_v<_T2>) && requires {
+      { *__x == *__y } -> __boolean_testable;
+      { __x.error() == __y.error() } -> __boolean_testable;
+    }
+  {
     if (__x.__has_val() != __y.__has_val()) {
       return false;
     } else {
@@ -1153,12 +1158,20 @@ class expected : private __expected_base<_Tp, _Err> {
   }
 
   template <class _T2>
-  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v) {
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v)
+    requires(!__is_std_expected<_T2>::value) && requires {
+      { *__x == __v } -> __boolean_testable;
+    }
+  {
     return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
   }
 
   template <class _E2>
-  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e) {
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e)
+    requires requires {
+      { __x.error() == __e.error() } -> __boolean_testable;
+    }
+  {
     return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
   }
 };
@@ -1851,7 +1864,11 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
   // [expected.void.eq], equality operators
   template <class _T2, class _E2>
     requires is_void_v<_T2>
-  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
+    requires requires {
+      { __x.error() == __y.error() } -> __boolean_testable;
+    }
+  {
     if (__x.__has_val() != __y.__has_val()) {
       return false;
     } else {
@@ -1860,7 +1877,11 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
   }
 
   template <class _E2>
-  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y) {
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y)
+    requires requires {
+      { __x.error() == __y.error() } -> __boolean_testable;
+    }
+  {
     return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
   }
 };
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
index bc8b9de97e4d2..2959834a85dd1 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
@@ -17,18 +17,17 @@
 #include <utility>
 
 #include "test_macros.h"
+#include "../../types.h"
 
-struct Data {
-  int i;
-  constexpr Data(int ii) : i(ii) {}
-
-  friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
-};
+// https://wg21.link/P3379R0
+static_assert(CanCompare<std::expected<int, int>, int>);
+static_assert(CanCompare<std::expected<int, int>, EqualityComparable>);
+static_assert(!CanCompare<std::expected<int, int>, NonComparable>);
 
 constexpr bool test() {
   // x.has_value()
   {
-    const std::expected<Data, int> e1(std::in_place, 5);
+    const std::expected<EqualityComparable, int> e1(std::in_place, 5);
     int i2 = 10;
     int i3 = 5;
     assert(e1 != i2);
@@ -37,7 +36,7 @@ constexpr bool test() {
 
   // !x.has_value()
   {
-    const std::expected<Data, int> e1(std::unexpect, 5);
+    const std::expected<EqualityComparable, int> e1(std::unexpect, 5);
     int i2 = 10;
     int i3 = 5;
     assert(e1 != i2);
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
index 9325c6c61ad2d..e2da668728e0d 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
@@ -18,19 +18,21 @@
 #include <utility>
 
 #include "test_macros.h"
+#include "../../types.h"
 
 // Test constraint
-template <class T1, class T2>
-concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
-
-struct Foo{};
-static_assert(!CanCompare<Foo, Foo>);
+static_assert(!CanCompare<NonComparable, NonComparable>);
 
 static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
 static_assert(CanCompare<std::expected<int, int>, std::expected<short, short>>);
 
-// Note this is true because other overloads are unconstrained
-static_assert(CanCompare<std::expected<int, int>, std::expected<void, int>>);
+// https://wg21.link/P3379R0
+static_assert(!CanCompare<std::expected<int, int>, std::expected<void, int>>);
+static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
+static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<NonComparable, int>>);
+static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<int, NonComparable>>);
+static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<int, NonComparable>>);
+static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<NonComparable, int>>);
 
 constexpr bool test() {
   // x.has_value() && y.has_value()
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
index a8c469d01be28..cd2db0efc48b1 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
@@ -17,18 +17,17 @@
 #include <utility>
 
 #include "test_macros.h"
+#include "../../types.h"
 
-struct Data {
-  int i;
-  constexpr Data(int ii) : i(ii) {}
-
-  friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
-};
+// https://wg21.link/P3379R0
+static_assert(CanCompare<std::expected<EqualityComparable, EqualityComparable>, std::unexpected<int>>);
+static_assert(CanCompare<std::expected<EqualityComparable, int>, std::unexpected<EqualityComparable>>);
+static_assert(!CanCompare<std::expected<EqualityComparable, NonComparable>, std::unexpected<int>>);
 
 constexpr bool test() {
   // x.has_value()
   {
-    const std::expected<Data, Data> e1(std::in_place, 5);
+    const std::expected<EqualityComparable, EqualityComparable> e1(std::in_place, 5);
     std::unexpected<int> un2(10);
     std::unexpected<int> un3(5);
     assert(e1 != un2);
@@ -37,7 +36,7 @@ constexpr bool test() {
 
   // !x.has_value()
   {
-    const std::expected<Data, Data> e1(std::unexpect, 5);
+    const std::expected<EqualityComparable, EqualityComparable> e1(std::unexpect, 5);
     std::unexpected<int> un2(10);
     std::unexpected<int> un3(5);
     assert(e1 != un2);
diff --git a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
index 8b24875586852..224cbc610e78b 100644
--- a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
@@ -18,10 +18,7 @@
 #include <utility>
 
 #include "test_macros.h"
-
-// Test constraint
-template <class T1, class T2>
-concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
+#include "../../types.h"
 
 struct Foo{};
 static_assert(!CanCompare<Foo, Foo>);
@@ -29,8 +26,13 @@ static_assert(!CanCompare<Foo, Foo>);
 static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
 static_assert(CanCompare<std::expected<void, int>, std::expected<void, short>>);
 
-// Note this is true because other overloads in expected<non-void> are unconstrained
-static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
+// https://wg21.link/P3379R0
+static_assert(!CanCompare<std::expected<void, int>, std::expected<int, int>>);
+static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
+static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
+static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, NonComparable>>);
+static_assert(!CanCompare<std::expected<void, int>, std::expected<void, NonComparable>>);
+static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, int>>);
 
 constexpr bool test() {
   // x.has_value() && y.has_value()
diff --git a/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp
index 4500971131b65..4d9afaf24e3a6 100644
--- a/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp
@@ -17,18 +17,17 @@
 #include <utility>
 
 #include "test_macros.h"
+#include "../../types.h"
 
-struct Data {
-  int i;
-  constexpr Data(int ii) : i(ii) {}
-
-  friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
-};
+// https://wg21.link/P3379R0
+static_assert(CanCompare<std::expected<EqualityComparable, EqualityComparable>, std::unexpected<int>>);
+static_assert(CanCompare<std::expected<EqualityComparable, int>, std::unexpected<EqualityComparable>>);
+static_assert(!CanCompare<std::expected<EqualityComparable, NonComparable>, std::unexpected<int>>);
 
 constexpr bool test() {
   // x.has_value()
   {
-    const std::expected<void, Data> e1;
+    const std::expected<void, EqualityComparable> e1;
     std::unexpected<int> un2(10);
     std::unexpected<int> un3(5);
     assert(e1 != un2);
@@ -37,7 +36,7 @@ constexpr bool test() {
 
   // !x.has_value()
   {
-    const std::expected<void, Data> e1(std::unexpect, 5);
+    const std::expected<void, EqualityComparable> e1(std::unexpect, 5);
     std::unexpected<int> un2(10);
     std::unexpected<int> un3(5);
     assert(e1 != un2);
diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h
index df73ebdfe495e..11473ca3d97de 100644
--- a/libcxx/test/std/utilities/expected/types.h
+++ b/libcxx/test/std/utilities/expected/types.h
@@ -336,4 +336,17 @@ struct CheckForInvalidWrites : public CheckForInvalidWritesBase<WithPaddedExpect
   }
 };
 
+struct NonComparable {};
+
+struct EqualityComparable {
+  int i;
+  constexpr EqualityComparable(int ii) : i(ii) {}
+
+  friend constexpr bool operator==(const EqualityComparable& data, int ii) { return data.i == ii; }
+};
+
+// Test constraint
+template <class T1, class T2>
+concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
+
 #endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H

>From 20fb88f6498c280aa72a6edef367ec539df49326 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 22 Apr 2025 22:35:02 +0800
Subject: [PATCH 2/2] [libc++] Introduce __core_convertible_to and make this
 paper as a C++26 feature

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 libcxx/include/CMakeLists.txt                 |  1 +
 .../include/__concepts/core_convertible_to.h  | 31 +++++++++++++++++++
 libcxx/include/__cxx03/module.modulemap       |  1 +
 libcxx/include/__expected/expected.h          | 31 +++++++++++++------
 libcxx/include/module.modulemap               |  1 +
 .../equality/equality.T2.pass.cpp             |  2 ++
 .../equality/equality.other_expected.pass.cpp |  6 +++-
 .../equality/equality.unexpected.pass.cpp     |  2 ++
 .../equality/equality.other_expected.pass.cpp |  5 +++
 .../gn/secondary/libcxx/include/BUILD.gn      |  1 +
 10 files changed, 71 insertions(+), 10 deletions(-)
 create mode 100644 libcxx/include/__concepts/core_convertible_to.h

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index c225131101ce2..ddbe4581353ff 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -307,6 +307,7 @@ set(files
   __concepts/constructible.h
   __concepts/convertible_to.h
   __concepts/copyable.h
+  __concepts/core_convertible_to.h
   __concepts/derived_from.h
   __concepts/destructible.h
   __concepts/different_from.h
diff --git a/libcxx/include/__concepts/core_convertible_to.h b/libcxx/include/__concepts/core_convertible_to.h
new file mode 100644
index 0000000000000..d093cdc10e7d9
--- /dev/null
+++ b/libcxx/include/__concepts/core_convertible_to.h
@@ -0,0 +1,31 @@
+#ifndef _LIBCPP___CONCEPTS_CORE_CONVERTIBLE_TO_H
+#define _LIBCPP___CONCEPTS_CORE_CONVERTIBLE_TO_H
+
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20
+
+// [conv.general]/3 says "E is convertible to T" whenever "T t=E;" is well-formed.
+// We can't test for that, but we can test implicit convertibility by passing it
+// to a function. Unlike std::convertible_to, __core_convertible_to doesn't test
+// static_cast or handle cv void, while accepting move-only types.
+//
+// This is a conceptual __is_core_convertible.
+template <class _Tp, class _Up>
+concept __core_convertible_to = requires {
+  // rejects function and array types which are adjusted to pointer types in parameter lists
+  static_cast<_Up (*)()>(nullptr)();
+  static_cast<void (*)(_Up)>(nullptr)(static_cast<_Tp (*)()>(nullptr)());
+};
+
+#endif
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___CONCEPTS_CORE_CONVERTIBLE_TO_H
diff --git a/libcxx/include/__cxx03/module.modulemap b/libcxx/include/__cxx03/module.modulemap
index 34a2d0f25fc45..61e717d188550 100644
--- a/libcxx/include/__cxx03/module.modulemap
+++ b/libcxx/include/__cxx03/module.modulemap
@@ -1190,6 +1190,7 @@ module cxx03_std_private_concepts_constructible         [system] {
 }
 module cxx03_std_private_concepts_convertible_to        [system] { header "__concepts/convertible_to.h" }
 module cxx03_std_private_concepts_copyable              [system] { header "__concepts/copyable.h" }
+module cxx03_std_private_concepts_core_convertible_to   [system] { header "__concepts/core_convertible_to.h" }
 module cxx03_std_private_concepts_derived_from          [system] { header "__concepts/derived_from.h" }
 module cxx03_std_private_concepts_destructible          [system] {
   header "__concepts/destructible.h"
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index f8af6429c05ae..4cd169dd0cb2f 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -10,7 +10,7 @@
 #define _LIBCPP___EXPECTED_EXPECTED_H
 
 #include <__assert>
-#include <__concepts/boolean_testable.h>
+#include <__concepts/core_convertible_to.h>
 #include <__config>
 #include <__expected/bad_expected_access.h>
 #include <__expected/unexpect.h>
@@ -1141,10 +1141,13 @@ class expected : private __expected_base<_Tp, _Err> {
   // [expected.object.eq], equality operators
   template <class _T2, class _E2>
   _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
-    requires(!is_void_v<_T2>) && requires {
-      { *__x == *__y } -> __boolean_testable;
-      { __x.error() == __y.error() } -> __boolean_testable;
-    }
+    requires(!is_void_v<_T2>)
+#  if _LIBCPP_STD_VER >= 26
+            && requires {
+                 { *__x == *__y } -> __core_convertible_to<bool>;
+                 { __x.error() == __y.error() } -> __core_convertible_to<bool>;
+               }
+#  endif
   {
     if (__x.__has_val() != __y.__has_val()) {
       return false;
@@ -1159,18 +1162,22 @@ class expected : private __expected_base<_Tp, _Err> {
 
   template <class _T2>
   _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v)
+#  if _LIBCPP_STD_VER >= 26
     requires(!__is_std_expected<_T2>::value) && requires {
-      { *__x == __v } -> __boolean_testable;
+      { *__x == __v } -> __core_convertible_to<bool>;
     }
+#  endif
   {
     return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
   }
 
   template <class _E2>
   _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e)
+#  if _LIBCPP_STD_VER >= 26
     requires requires {
-      { __x.error() == __e.error() } -> __boolean_testable;
+      { __x.error() == __e.error() } -> __core_convertible_to<bool>;
     }
+#  endif
   {
     return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
   }
@@ -1865,9 +1872,12 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
   template <class _T2, class _E2>
     requires is_void_v<_T2>
   _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
+#  if _LIBCPP_STD_VER >= 26
+
     requires requires {
-      { __x.error() == __y.error() } -> __boolean_testable;
+      { __x.error() == __y.error() } -> __core_convertible_to<bool>;
     }
+#  endif
   {
     if (__x.__has_val() != __y.__has_val()) {
       return false;
@@ -1878,9 +1888,12 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
 
   template <class _E2>
   _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y)
+#  if _LIBCPP_STD_VER >= 26
+
     requires requires {
-      { __x.error() == __y.error() } -> __boolean_testable;
+      { __x.error() == __y.error() } -> __core_convertible_to<bool>;
     }
+#  endif
   {
     return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
   }
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 65123eb268150..5d42bb5f27985 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -1074,6 +1074,7 @@ module std [system] {
     module constructible            { header "__concepts/constructible.h" }
     module convertible_to           { header "__concepts/convertible_to.h" }
     module copyable                 { header "__concepts/copyable.h" }
+    module core_convertible_to      { header "__concepts/core_convertible_to.h" }
     module derived_from             { header "__concepts/derived_from.h" }
     module destructible             { header "__concepts/destructible.h" }
     module different_from           { header "__concepts/different_from.h" }
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
index 2959834a85dd1..25eb97a2df4d3 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
@@ -19,10 +19,12 @@
 #include "test_macros.h"
 #include "../../types.h"
 
+#if TEST_STD_VER >= 26
 // https://wg21.link/P3379R0
 static_assert(CanCompare<std::expected<int, int>, int>);
 static_assert(CanCompare<std::expected<int, int>, EqualityComparable>);
 static_assert(!CanCompare<std::expected<int, int>, NonComparable>);
+#endif
 
 constexpr bool test() {
   // x.has_value()
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
index e2da668728e0d..f0f549b6b7772 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
@@ -26,6 +26,7 @@ static_assert(!CanCompare<NonComparable, NonComparable>);
 static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
 static_assert(CanCompare<std::expected<int, int>, std::expected<short, short>>);
 
+#if TEST_STD_VER >= 26
 // https://wg21.link/P3379R0
 static_assert(!CanCompare<std::expected<int, int>, std::expected<void, int>>);
 static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
@@ -33,7 +34,10 @@ static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<NonCo
 static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<int, NonComparable>>);
 static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<int, NonComparable>>);
 static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<NonComparable, int>>);
-
+#else
+// Note this is true because other overloads in expected<non-void> are unconstrained
+static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
+#endif
 constexpr bool test() {
   // x.has_value() && y.has_value()
   {
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
index cd2db0efc48b1..6c7d2f39514e7 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
@@ -19,10 +19,12 @@
 #include "test_macros.h"
 #include "../../types.h"
 
+#if TEST_STD_VER >= 26
 // https://wg21.link/P3379R0
 static_assert(CanCompare<std::expected<EqualityComparable, EqualityComparable>, std::unexpected<int>>);
 static_assert(CanCompare<std::expected<EqualityComparable, int>, std::unexpected<EqualityComparable>>);
 static_assert(!CanCompare<std::expected<EqualityComparable, NonComparable>, std::unexpected<int>>);
+#endif
 
 constexpr bool test() {
   // x.has_value()
diff --git a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
index 224cbc610e78b..b6c3d8deee644 100644
--- a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
@@ -26,6 +26,7 @@ static_assert(!CanCompare<Foo, Foo>);
 static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
 static_assert(CanCompare<std::expected<void, int>, std::expected<void, short>>);
 
+#if TEST_STD_VER >= 26
 // https://wg21.link/P3379R0
 static_assert(!CanCompare<std::expected<void, int>, std::expected<int, int>>);
 static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
@@ -33,6 +34,10 @@ static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
 static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, NonComparable>>);
 static_assert(!CanCompare<std::expected<void, int>, std::expected<void, NonComparable>>);
 static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, int>>);
+#else
+// Note this is true because other overloads in expected<non-void> are unconstrained
+static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
+#endif
 
 constexpr bool test() {
   // x.has_value() && y.has_value()
diff --git a/llvm/utils/gn/secondary/libcxx/include/BUILD.gn b/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
index 542fa9d94a39e..3f0a12544c307 100644
--- a/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
+++ b/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
@@ -375,6 +375,7 @@ if (current_toolchain == default_toolchain) {
       "__concepts/constructible.h",
       "__concepts/convertible_to.h",
       "__concepts/copyable.h",
+      "__concepts/core_convertible_to.h",
       "__concepts/derived_from.h",
       "__concepts/destructible.h",
       "__concepts/different_from.h",



More information about the llvm-commits mailing list