-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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
[libc++] restrict the expected conversion constructor not compete against copy constructor #96101
Conversation
@llvm/pr-subscribers-libcxx Author: Hui (huixie90) Changesfixes #92676 Full diff: https://github.com/llvm/llvm-project/pull/96101.diff 2 Files Affected:
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 0f994e297a877..da0a0fa85714c 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -507,7 +507,9 @@ class expected : private __expected_base<_Tp, _Err> {
_And< is_constructible<_Tp, _UfQual>,
is_constructible<_Err, _OtherErrQual>,
_If<_Not<is_same<remove_cv_t<_Tp>, bool>>::value,
- _And< _Not<is_constructible<_Tp, expected<_Up, _OtherErr>&>>,
+ _And<
+ _Not<_And<is_same<_Tp, _Up>, is_same<_Err, _OtherErr>>>,
+ _Not<is_constructible<_Tp, expected<_Up, _OtherErr>&>>,
_Not<is_constructible<_Tp, expected<_Up, _OtherErr>>>,
_Not<is_constructible<_Tp, const expected<_Up, _OtherErr>&>>,
_Not<is_constructible<_Tp, const expected<_Up, _OtherErr>>>,
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 581df51207da2..6c278be9de98a 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,17 @@ static_assert(!std::is_trivially_copy_constructible_v<std::expected<CopyableNonT
static_assert(!std::is_trivially_copy_constructible_v<std::expected<int, CopyableNonTrivial>>);
static_assert(!std::is_trivially_copy_constructible_v<std::expected<CopyableNonTrivial, CopyableNonTrivial>>);
+
+struct Any {
+ constexpr Any() = default;
+ constexpr Any(const Any&) = default;
+ constexpr Any& operator=(const Any&)=default;
+
+ template <class T>
+ requires (!std::is_same_v<Any, std::decay_t<T>> && std::is_copy_constructible_v<std::decay_t<T>>)
+ constexpr Any(T&&){}
+};
+
constexpr bool test() {
// copy the value non-trivial
{
@@ -109,6 +120,13 @@ constexpr bool test() {
assert(!e2.has_value());
}
+ {
+ // https://github.com/llvm/llvm-project/issues/92676
+ std::expected<Any, int> e1;
+ auto e2 = e1;
+ assert(e2.has_value());
+ }
+
return true;
}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM with green CI and comments applied.
Also, please file a bug report against Clang, it seems like this could be a compiler issue: https://godbolt.org/z/83Yfsvbr5
If this isn't a compiler issue, then this will likely lead to a LWG issue because this code should definitely compile.
libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp
Outdated
Show resolved
Hide resolved
…inst copy constructor
05b308a
to
0223db9
Compare
…inst copy constructor (llvm#96101) fixes llvm#92676 So right now clang does not like ``` std::expected<std::any, int> 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<is_constructible<_Tp, expected<_Up, _OtherErr>&>> ``` which is checking ``` is_constructible<std::any, expected<_Up, _OtherErr>&> ``` 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<std::expected<std::any, int>> ``` 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<T, E>`, we give up immediately and let the copy constructor to deal with it
…inst copy constructor (llvm#96101) fixes llvm#92676 So right now clang does not like ``` std::expected<std::any, int> 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<is_constructible<_Tp, expected<_Up, _OtherErr>&>> ``` which is checking ``` is_constructible<std::any, expected<_Up, _OtherErr>&> ``` 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<std::expected<std::any, int>> ``` 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<T, E>`, we give up immediately and let the copy constructor to deal with it
fixes #92676
So right now clang does not like
So basically when clang tries to do overload resolution of
auto e2 = e1;
It finds
Then in
__can_convert
, one of the check iswhich is checking
Then it looks at
std::any
's constructorIn the above,
is_copy_constructible<_Tp>
expands toAnd the above goes back to the original thing we asked : copy the
std::expected
, which goes to the overload resolution again.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<T, E>
, we give up immediately and let the copy constructor to deal with it