From 16f349251fabacfdba4acac3b25baf0e6890c1a0 Mon Sep 17 00:00:00 2001 From: Hui Date: Wed, 26 Jun 2024 12:10:59 +0100 Subject: [PATCH] [libc++] restrict the expected conversion constructor not compete against copy constructor (#96101) fixes #92676 So right now clang does not like ``` std::expected e1; auto e2 = e1; ``` So basically when clang tries to do overload resolution of `auto e2 = e1;` It finds ``` expected(const expected&); // 1. This is OK expected(const expected<_Up, _OtherErr>&) requires __can_convert; // 2. This needs to check its constraints ``` Then in `__can_convert`, one of the check is ``` _Not&>> ``` which is checking ``` is_constructible&> ``` Then it looks at `std::any`'s constructor ``` template < class _ValueType, class _Tp = decay_t<_ValueType>, class = enable_if_t< !is_same<_Tp, any>::value && !__is_inplace_type<_ValueType>::value && is_copy_constructible<_Tp>::value> > any(_ValueType&& __value); ``` In the above, `is_copy_constructible<_Tp>` expands to ``` is_copy_constructible> ``` And the above goes back to the original thing we asked : copy the `std::expected`, which goes to the overload resolution again. ``` expected(const expected&); expected(const expected<_Up, _OtherErr>&) requires __can_convert; ``` So the second overload results in a logical cycle. I am not a language lawyer. We could argue that clang should give up on the second overload which has logical cycle, as the first overload is a perfect match. Anyway, the fix in this patch tries to short-circuiting the second overload's constraint check: that is, if the argument matches exact same `expected`, we give up immediately and let the copy constructor to deal with it --- libcxx/include/__expected/expected.h | 4 +++- .../expected.expected/ctor/ctor.copy.pass.cpp | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h index 0f994e297a87..f618b20603e6 100644 --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -507,7 +507,9 @@ private: _And< is_constructible<_Tp, _UfQual>, is_constructible<_Err, _OtherErrQual>, _If<_Not, bool>>::value, - _And< _Not&>>, + _And< + _Not<_And, is_same<_Err, _OtherErr>>>, // use the copy constructor instead, see #92676 + _Not&>>, _Not>>, _Not&>>, _Not>>, diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp index 581df51207da..ba9831750c84 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp @@ -62,6 +62,16 @@ static_assert(!std::is_trivially_copy_constructible_v>); static_assert(!std::is_trivially_copy_constructible_v>); +struct Any { + constexpr Any() = default; + constexpr Any(const Any&) = default; + constexpr Any& operator=(const Any&) = default; + + template + requires(!std::is_same_v> && std::is_copy_constructible_v>) + constexpr Any(T&&) {} +}; + constexpr bool test() { // copy the value non-trivial { @@ -109,6 +119,16 @@ constexpr bool test() { assert(!e2.has_value()); } + { + // TODO(LLVM 20): Remove once we drop support for Clang 17 +#if defined(TEST_CLANG_VER) && TEST_CLANG_VER >= 1800 + // https://github.com/llvm/llvm-project/issues/92676 + std::expected e1; + auto e2 = e1; + assert(e2.has_value()); +#endif + } + return true; }