[libcxx-commits] [libcxx] [libc++] Fix constraint recursion in std::expected's operator== (PR #201455)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jun 4 07:00:44 PDT 2026
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/201455
>From 496adca92b3c69c49cd0e890538a62987261ee18 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 3 Jun 2026 16:11:31 -0400
Subject: [PATCH 1/2] [libc++] Fix constraint recursion in std::expected's
operator==
The C++26 constraint added to operator==(const expected& x, const T2& v)
by P3379R0 evaluates *x == v as part of constraint satisfaction. When
ADL on a comparison reaches this hidden friend through a type whose
associated namespaces include std::expected -- for example
std::pair<T, std::expected<U, V>> -- the constraint check ends up
considering the same overload again with the original type as T2,
producing a "satisfaction of constraint depends on itself" error.
Parameterize the expected operand with an extra template parameter
constrained to be the same type as the enclosing expected's value type.
This is observationally equivalent but makes template argument deduction
fail for non-expected operands before the constraint is evaluated, so
the recursion never starts.
Fixes #160431
rdar://178226313
Assisted-by: Claude
---
libcxx/include/__expected/expected.h | 7 +++++--
.../expected.expected/equality/equality.T2.pass.cpp | 11 +++++++++++
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 24ae33d4e3af8..063f1c2f7e630 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -1161,8 +1161,11 @@ class expected : private __expected_base<_Tp, _Err> {
}
}
- template <class _T2>
- _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v)
+ // The unusual signature avoids constraint recursion via ADL through
+ // std::expected, see https://github.com/llvm/llvm-project/issues/160431.
+ template <class _T2, class _Tp2>
+ requires _IsSame<_Tp2, _Tp>::value
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected<_Tp2, _Err>& __x, const _T2& __v)
# if _LIBCPP_STD_VER >= 26
requires(!__is_std_expected<_T2>::value) && requires {
{ *__x == __v } -> __core_convertible_to<bool>;
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 16c6986ae670e..53451be96144e 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
@@ -45,6 +45,17 @@ constexpr bool test() {
assert(e1 != i3);
}
+#if TEST_STD_VER >= 26
+ // Regression test for https://github.com/llvm/llvm-project/issues/160431:
+ // the constraint of this overload would recurse when ADL on the comparison
+ // found the same hidden friend through a type whose associated namespaces
+ // reach std::expected.
+ {
+ std::pair<int, std::expected<int, int>> p1, p2;
+ assert(p1 == p2);
+ }
+#endif
+
return true;
}
>From 7527d50e7714a3efb9294dd6b97ef79c23690912 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 4 Jun 2026 10:00:29 -0400
Subject: [PATCH 2/2] Address review comments
---
libcxx/include/__expected/expected.h | 7 ++++---
.../expected.expected/equality/equality.T2.pass.cpp | 7 +++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 063f1c2f7e630..27bc086698399 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/same_as.h>
#include <__config>
#include <__expected/bad_expected_access.h>
#include <__expected/unexpect.h>
@@ -1162,9 +1163,9 @@ class expected : private __expected_base<_Tp, _Err> {
}
// The unusual signature avoids constraint recursion via ADL through
- // std::expected, see https://github.com/llvm/llvm-project/issues/160431.
- template <class _T2, class _Tp2>
- requires _IsSame<_Tp2, _Tp>::value
+ // std::expected, see https://llvm.org/PR160431. Note that this only
+ // triggers with compilers that implement https://wg21.link/CWG2369.
+ template <class _T2, same_as<_Tp> _Tp2>
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected<_Tp2, _Err>& __x, const _T2& __v)
# if _LIBCPP_STD_VER >= 26
requires(!__is_std_expected<_T2>::value) && requires {
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 53451be96144e..6a57dc4643354 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
@@ -46,10 +46,9 @@ constexpr bool test() {
}
#if TEST_STD_VER >= 26
- // Regression test for https://github.com/llvm/llvm-project/issues/160431:
- // the constraint of this overload would recurse when ADL on the comparison
- // found the same hidden friend through a type whose associated namespaces
- // reach std::expected.
+ // Regression test for https://llvm.org/PR160431: the constraint of this overload
+ // would recurse when ADL on the comparison found the same hidden friend through a
+ // type whose associated namespaces reach std::expected.
{
std::pair<int, std::expected<int, int>> p1, p2;
assert(p1 == p2);
More information about the libcxx-commits
mailing list