Skip to content

Commit 1e02158

Browse files
committed
[libc++] Implement LWG3843 (std::expected<T,E>::value() & assumes E is copy constructible)
Implement LWG3843 (std::expected<T,E>::value() & assumes E is copy constructible) https://wg21.link/LWG3843 Reviewed By: #libc, ldionne Differential Revision: https://reviews.llvm.org/D154110
1 parent 01e3393 commit 1e02158

File tree

4 files changed

+165
-11
lines changed

4 files changed

+165
-11
lines changed

libcxx/docs/Status/Cxx23Issues.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@
291291
"`3828 <https://wg21.link/LWG3828>`__","Sync ``intmax_t`` and ``uintmax_t`` with C2x","February 2023","","",""
292292
"`3833 <https://wg21.link/LWG3833>`__","Remove specialization ``template<size_t N> struct formatter<const charT[N], charT>``","February 2023","|Complete|","17.0","|format|"
293293
"`3836 <https://wg21.link/LWG3836>`__","``std::expected<bool, E1>`` conversion constructor ``expected(const expected<U, G>&)`` should take precedence over ``expected(U&&)`` with operator ``bool``","February 2023","","",""
294-
"`3843 <https://wg21.link/LWG3843>`__","``std::expected<T,E>::value() &`` assumes ``E`` is copy constructible","February 2023","","",""
294+
"`3843 <https://wg21.link/LWG3843>`__","``std::expected<T,E>::value() &`` assumes ``E`` is copy constructible","February 2023","|Complete|","17.0",""
295295
"`3847 <https://wg21.link/LWG3847>`__","``ranges::to`` can still return views","February 2023","","","|ranges|"
296296
"`3862 <https://wg21.link/LWG3862>`__","``basic_const_iterator``'s ``common_type`` specialization is underconstrained","February 2023","","",""
297297
"`3865 <https://wg21.link/LWG3865>`__","Sorting a range of ``pairs``","February 2023","|Complete|","17.0","|ranges|"

libcxx/include/__expected/expected.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include <__type_traits/negation.h>
4747
#include <__type_traits/remove_cv.h>
4848
#include <__type_traits/remove_cvref.h>
49+
#include <__utility/as_const.h>
4950
#include <__utility/exception_guard.h>
5051
#include <__utility/forward.h>
5152
#include <__utility/in_place.h>
@@ -559,29 +560,35 @@ class expected {
559560
_LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __has_val_; }
560561

561562
_LIBCPP_HIDE_FROM_ABI constexpr const _Tp& value() const& {
563+
static_assert(is_copy_constructible_v<_Err>, "error_type has to be copy constructible");
562564
if (!__has_val_) {
563-
std::__throw_bad_expected_access<_Err>(__union_.__unex_);
565+
std::__throw_bad_expected_access<_Err>(std::as_const(error()));
564566
}
565567
return __union_.__val_;
566568
}
567569

568570
_LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() & {
571+
static_assert(is_copy_constructible_v<_Err>, "error_type has to be copy constructible");
569572
if (!__has_val_) {
570-
std::__throw_bad_expected_access<_Err>(__union_.__unex_);
573+
std::__throw_bad_expected_access<_Err>(std::as_const(error()));
571574
}
572575
return __union_.__val_;
573576
}
574577

575578
_LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& value() const&& {
579+
static_assert(is_copy_constructible_v<_Err> && is_constructible_v<_Err, decltype(std::move(error()))>,
580+
"error_type has to be both copy constructible and constructible from decltype(std::move(error()))");
576581
if (!__has_val_) {
577-
std::__throw_bad_expected_access<_Err>(std::move(__union_.__unex_));
582+
std::__throw_bad_expected_access<_Err>(std::move(error()));
578583
}
579584
return std::move(__union_.__val_);
580585
}
581586

582587
_LIBCPP_HIDE_FROM_ABI constexpr _Tp&& value() && {
588+
static_assert(is_copy_constructible_v<_Err> && is_constructible_v<_Err, decltype(std::move(error()))>,
589+
"error_type has to be both copy constructible and constructible from decltype(std::move(error()))");
583590
if (!__has_val_) {
584-
std::__throw_bad_expected_access<_Err>(std::move(__union_.__unex_));
591+
std::__throw_bad_expected_access<_Err>(std::move(error()));
585592
}
586593
return std::move(__union_.__val_);
587594
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
10+
11+
// Test the mandates
12+
// constexpr T& value() & ;
13+
// constexpr const T& value() const &;
14+
// Mandates: is_copy_constructible_v<E> is true.
15+
16+
// constexpr T&& value() &&;
17+
// constexpr const T&& value() const &&;
18+
// Mandates: is_copy_constructible_v<E> is true and is_constructible_v<E, decltype(std::move(error()))> is true.
19+
20+
#include <expected>
21+
#include <utility>
22+
23+
#include "MoveOnly.h"
24+
25+
struct CopyConstructible {
26+
constexpr CopyConstructible() = default;
27+
constexpr CopyConstructible(const CopyConstructible&) = default;
28+
};
29+
30+
struct CopyConstructibleButNotMoveConstructible {
31+
constexpr CopyConstructibleButNotMoveConstructible() = default;
32+
constexpr CopyConstructibleButNotMoveConstructible(const CopyConstructibleButNotMoveConstructible&) = default;
33+
constexpr CopyConstructibleButNotMoveConstructible(CopyConstructibleButNotMoveConstructible&&) = delete;
34+
constexpr CopyConstructibleButNotMoveConstructible(const CopyConstructibleButNotMoveConstructible&&) = delete;
35+
};
36+
37+
struct CopyConstructibleAndMoveConstructible {
38+
constexpr CopyConstructibleAndMoveConstructible() = default;
39+
constexpr CopyConstructibleAndMoveConstructible(const CopyConstructibleAndMoveConstructible&) = default;
40+
constexpr CopyConstructibleAndMoveConstructible(CopyConstructibleAndMoveConstructible&&) = default;
41+
};
42+
43+
// clang-format off
44+
void test() {
45+
46+
// Test & overload
47+
{
48+
// is_copy_constructible_v<E> is true.
49+
{
50+
std::expected<int, CopyConstructible> e;
51+
[[maybe_unused]] auto val = e.value();
52+
}
53+
54+
// is_copy_constructible_v<E> is false.
55+
{
56+
std::expected<int, MoveOnly> e;
57+
[[maybe_unused]] auto val = e.value();
58+
// expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be copy constructible}}
59+
}
60+
}
61+
62+
// Test const& overload
63+
{
64+
// is_copy_constructible_v<E> is true.
65+
{
66+
const std::expected<int, CopyConstructible> e;
67+
[[maybe_unused]] auto val = e.value();
68+
}
69+
70+
// is_copy_constructible_v<E> is false.
71+
{
72+
const std::expected<int, MoveOnly> e;
73+
[[maybe_unused]] auto val = e.value();
74+
// expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be copy constructible}}
75+
}
76+
}
77+
78+
// Test && overload
79+
{
80+
// is_copy_constructible_v<E> is false.
81+
{
82+
std::expected<int, MoveOnly> e;
83+
[[maybe_unused]] auto val = std::move(e).value();
84+
// expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}}
85+
}
86+
87+
// is_copy_constructible_v<E> is true and is_constructible_v<E, decltype(std::move(error()))> is true.
88+
{
89+
std::expected<int, CopyConstructibleAndMoveConstructible> e;
90+
[[maybe_unused]] auto val = std::move(e).value();
91+
}
92+
93+
// is_copy_constructible_v<E> is true and is_constructible_v<E, decltype(std::move(error()))> is false.
94+
{
95+
std::expected<int, CopyConstructibleButNotMoveConstructible> e;
96+
[[maybe_unused]] auto val = std::move(e).value();
97+
// expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}}
98+
}
99+
}
100+
101+
// Test const&& overload
102+
{
103+
// is_copy_constructible_v<E> is false.
104+
{
105+
const std::expected<int, MoveOnly> e;
106+
[[maybe_unused]] auto val = std::move(e).value();
107+
// expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}}
108+
}
109+
110+
// is_copy_constructible_v<E> is true and is_constructible_v<E, decltype(std::move(error()))> is true.
111+
{
112+
const std::expected<int, CopyConstructibleAndMoveConstructible> e;
113+
[[maybe_unused]] auto val = std::move(e).value();
114+
}
115+
116+
// is_copy_constructible_v<E> is true and is_constructible_v<E, decltype(std::move(error()))> is false.
117+
{
118+
const std::expected<int, CopyConstructibleButNotMoveConstructible> e;
119+
[[maybe_unused]] auto val = std::move(e).value();
120+
// expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}}
121+
}
122+
}
123+
// These diagnostics happen when we try to construct bad_expected_access from the non copy-constructible error type.
124+
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
125+
// expected-error-re@*:* {{call to deleted constructor of{{.*}}}}
126+
// expected-error-re@*:* {{call to deleted constructor of{{.*}}}}
127+
// expected-error-re@*:* {{call to deleted constructor of{{.*}}}}
128+
// expected-error-re@*:* {{call to deleted constructor of{{.*}}}}
129+
#endif
130+
}
131+
// clang-format on

libcxx/test/std/utilities/expected/expected.expected/observers/value.pass.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include <type_traits>
1919
#include <utility>
2020

21-
#include "MoveOnly.h"
2221
#include "test_macros.h"
2322

2423
constexpr bool test() {
@@ -75,23 +74,40 @@ void testException() {
7574
}
7675
}
7776

78-
// MoveOnly
77+
#endif // TEST_HAS_NO_EXCEPTIONS
78+
}
79+
80+
void testAsConst() {
81+
#ifndef TEST_HAS_NO_EXCEPTIONS
82+
struct Error {
83+
enum { Default, MutableRefCalled, ConstRefCalled } From = Default;
84+
Error() = default;
85+
Error(const Error&) { From = ConstRefCalled; }
86+
Error(Error&) { From = MutableRefCalled; }
87+
Error(Error&& e) { From = e.From; }
88+
};
89+
90+
// Test & overload
7991
{
80-
std::expected<int, MoveOnly> e(std::unexpect, 5);
92+
std::expected<int, Error> e(std::unexpect, Error());
8193
try {
82-
(void) std::move(e).value();
94+
(void)e.value();
8395
assert(false);
84-
} catch (const std::bad_expected_access<MoveOnly>& ex) {
85-
assert(ex.error() == 5);
96+
} catch (const std::bad_expected_access<Error>& ex) {
97+
assert(ex.error().From == Error::ConstRefCalled);
8698
}
8799
}
88100

101+
// There are no effects for `const &` overload.
102+
89103
#endif // TEST_HAS_NO_EXCEPTIONS
90104
}
91105

92106
int main(int, char**) {
93107
test();
94108
static_assert(test());
95109
testException();
110+
testAsConst();
111+
96112
return 0;
97113
}

0 commit comments

Comments
 (0)