Skip to content

[libc++] Implement LWG3940: std::expected<void, E>::value() also needs E to be copy constructible #71819

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

Merged
merged 11 commits into from
Jan 19, 2024
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cIssues.csv
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"`3927 <https://wg21.link/LWG3927>`__","Unclear preconditions for ``operator[]`` for sequence containers","Varna June 2023","|Nothing To Do|","",""
"`3935 <https://wg21.link/LWG3935>`__","``template<class X> constexpr complex& operator=(const complex<X>&)`` has no specification","Varna June 2023","|Complete|","3.4",""
"`3938 <https://wg21.link/LWG3938>`__","Cannot use ``std::expected`` monadic ops with move-only ``error_type``","Varna June 2023","|Complete|","18.0",""
"`3940 <https://wg21.link/LWG3940>`__","``std::expected<void, E>::value()`` also needs ``E`` to be copy constructible","Varna June 2023","","",""
"`3940 <https://wg21.link/LWG3940>`__","``std::expected<void, E>::value()`` also needs ``E`` to be copy constructible","Varna June 2023","|Complete|","18.0",""
"","","","","",""
"`2392 <https://wg21.link/LWG2392>`__","""character type"" is used but not defined","Kona November 2023","","",""
"`3203 <https://wg21.link/LWG3203>`__","``span`` element access invalidation","Kona November 2023","","",""
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/__expected/expected.h
Original file line number Diff line number Diff line change
Expand Up @@ -1150,12 +1150,14 @@ class expected<_Tp, _Err> {
}

_LIBCPP_HIDE_FROM_ABI constexpr void value() const& {
static_assert(is_copy_constructible_v<_Err>);
if (!__has_val_) {
std::__throw_bad_expected_access<_Err>(__union_.__unex_);
}
}

_LIBCPP_HIDE_FROM_ABI constexpr void value() && {
static_assert(is_copy_constructible_v<_Err> && is_move_constructible_v<_Err>);
if (!__has_val_) {
std::__throw_bad_expected_access<_Err>(std::move(__union_.__unex_));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//===----------------------------------------------------------------------===//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
// UNSUPPORTED: no-rtti
// UNSUPPORTED: no-exceptions

// constexpr void value() const &;
// Mandates: is_copy_constructible_v<E> is true.

// constexpr void value() &&;
// Mandates: is_copy_constructible_v<E> is true and is_move_constructible_v<E> is true.

#include <expected>

#include "MoveOnly.h"

struct CopyOnly {
CopyOnly() = default;
CopyOnly(const CopyOnly&) = default;
CopyOnly(CopyOnly&&) = delete;
};

void test() {
// MoveOnly type as error_type
std::expected<void, MoveOnly> e(std::unexpect, 5);

e.value(); // expected-note {{in instantiation of member function 'std::expected<void, MoveOnly>::value' requested here}}
// expected-error@*:* {{static assertion failed due to requirement 'is_copy_constructible_v<MoveOnly>'}}
// expected-error@*:* {{call to deleted constructor of 'MoveOnly'}}

std::move(e)
.value(); // expected-note {{in instantiation of member function 'std::expected<void, MoveOnly>::value' requested here}}
// expected-error@*:* {{static assertion failed due to requirement 'is_copy_constructible_v<MoveOnly>'}}

// CopyOnly type as error_type
std::expected<void, CopyOnly> e2(std::unexpect);
// expected-error@*:* {{call to deleted constructor of 'CopyOnly'}}

e2.value();

std::move(e2)
.value(); // expected-note {{in instantiation of member function 'std::expected<void, CopyOnly>::value' requested here}}
// expected-error@*:* {{static assertion failed due to requirement 'is_move_constructible_v<CopyOnly>'}}
// expected-error@*:* {{call to deleted constructor of 'CopyOnly'}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,6 @@ void testException() {
}
}

// MoveOnly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, according to the LWG issue, this test should never work. Do you know why there was not an issue before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah due to LWG3940 it cannot pass the compiler. But I guess before this the expected::value implementation can actually accept move-only types.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, according to the LWG issue, this test should never work. Do you know why there was not an issue before?

This is because of Clang's non-conforming acception of move-only exception types, which is reported as #53224.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would this extension be non-conforming? A warning is missing, but other than that I don't see how it wouldn't be conforming.

Copy link
Contributor

@frederick-vs-ja frederick-vs-ja Dec 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that throw NonCopyable(args) also needs to result in substitution failure, while Clang currently disagrees (example). See also this article.

{
std::expected<void, MoveOnly> e(std::unexpect, 5);
try {
std::move(e).value();
assert(false);
} catch (const std::bad_expected_access<MoveOnly>& ex) {
assert(ex.error() == 5);
}
}

#endif // TEST_HAS_NO_EXCEPTIONS
}

Expand Down