1 | //===----------------------------------------------------------------------===// |
2 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
3 | // See https://llvm.org/LICENSE.txt for license information. |
4 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
5 | // |
6 | //===----------------------------------------------------------------------===// |
7 | |
8 | // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 |
9 | |
10 | // template<class G> |
11 | // constexpr expected& operator=(unexpected<G>&& e); |
12 | // |
13 | // Let GF be G |
14 | // Constraints: |
15 | // - is_constructible_v<E, GF> is true; and |
16 | // - is_assignable_v<E&, GF> is true; and |
17 | // - is_nothrow_constructible_v<E, GF> || is_nothrow_move_constructible_v<T> || |
18 | // is_nothrow_move_constructible_v<E> is true. |
19 | // |
20 | // Effects: |
21 | // - If has_value() is true, equivalent to: |
22 | // reinit-expected(unex, val, std::forward<GF>(e.error())); |
23 | // has_val = false; |
24 | // - Otherwise, equivalent to: unex = std::forward<GF>(e.error()); |
25 | // Returns: *this. |
26 | |
27 | #include <cassert> |
28 | #include <concepts> |
29 | #include <expected> |
30 | #include <type_traits> |
31 | #include <utility> |
32 | |
33 | #include "../../types.h" |
34 | #include "test_macros.h" |
35 | |
36 | struct NotMoveConstructible { |
37 | NotMoveConstructible(NotMoveConstructible&&) = delete; |
38 | NotMoveConstructible& operator=(NotMoveConstructible&&) = default; |
39 | }; |
40 | |
41 | struct NotMoveAssignable { |
42 | NotMoveAssignable(NotMoveAssignable&&) = default; |
43 | NotMoveAssignable& operator=(NotMoveAssignable&&) = delete; |
44 | }; |
45 | |
46 | struct MoveMayThrow { |
47 | MoveMayThrow(MoveMayThrow const&) = default; |
48 | MoveMayThrow& operator=(const MoveMayThrow&) = default; |
49 | MoveMayThrow(MoveMayThrow&&) noexcept(false) {} |
50 | MoveMayThrow& operator=(MoveMayThrow&&) noexcept(false) { return *this; } |
51 | }; |
52 | |
53 | // Test constraints |
54 | static_assert(std::is_assignable_v<std::expected<int, int>&, std::unexpected<int>&&>); |
55 | |
56 | // !is_constructible_v<E, GF> |
57 | static_assert( |
58 | !std::is_assignable_v<std::expected<int, NotMoveConstructible>&, std::unexpected<NotMoveConstructible>&&>); |
59 | |
60 | // !is_assignable_v<E&, GF> |
61 | static_assert(!std::is_assignable_v<std::expected<int, NotMoveAssignable>&, std::unexpected<NotMoveAssignable>&&>); |
62 | |
63 | template <bool moveNoexcept, bool convertNoexcept> |
64 | struct MaybeNoexcept { |
65 | explicit MaybeNoexcept(int) noexcept(convertNoexcept); |
66 | MaybeNoexcept(MaybeNoexcept&&) noexcept(moveNoexcept); |
67 | MaybeNoexcept& operator=(MaybeNoexcept&&) = default; |
68 | MaybeNoexcept& operator=(int); |
69 | }; |
70 | |
71 | // !is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<T> && |
72 | // is_nothrow_move_constructible_v<E> |
73 | static_assert(std::is_assignable_v<std::expected<MaybeNoexcept<false, false>, MaybeNoexcept<true, false>>&, |
74 | std::unexpected<int>&&>); |
75 | |
76 | // is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<T> && |
77 | // !is_nothrow_move_constructible_v<E> |
78 | static_assert(std::is_assignable_v<std::expected<MaybeNoexcept<false, false>, MaybeNoexcept<false, true>>&, |
79 | std::unexpected<int>&&>); |
80 | |
81 | // !is_nothrow_constructible_v<E, GF> && is_nothrow_move_constructible_v<T> && |
82 | // !is_nothrow_move_constructible_v<E> |
83 | static_assert(std::is_assignable_v<std::expected<MaybeNoexcept<true, true>, MaybeNoexcept<false, false>>&, |
84 | std::unexpected<int>&&>); |
85 | |
86 | // !is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<T> && |
87 | // !is_nothrow_move_constructible_v<E> |
88 | static_assert(!std::is_assignable_v<std::expected<MaybeNoexcept<false, false>, MaybeNoexcept<false, false>>&, |
89 | std::unexpected<int>&&>); |
90 | |
91 | constexpr bool test() { |
92 | // - If has_value() is true, equivalent to: |
93 | // reinit-expected(unex, val, std::forward<GF>(e.error())); |
94 | // is_nothrow_constructible_v<E, GF> |
95 | // |
96 | // In this case, it should call the branch |
97 | // destroy_at(addressof(oldval)); |
98 | // construct_at(addressof(newval), std::forward<Args>(args)...); |
99 | { |
100 | BothNoexcept::state oldState{}; |
101 | std::expected<BothNoexcept, BothNoexcept> e(std::in_place, oldState, 5); |
102 | std::unexpected<int> un(10); |
103 | decltype(auto) x = (e = std::move(un)); |
104 | static_assert(std::same_as<decltype(x), std::expected<BothNoexcept, BothNoexcept>&>); |
105 | assert(&x == &e); |
106 | |
107 | assert(!oldState.moveCtorCalled); |
108 | assert(oldState.dtorCalled); |
109 | assert(e.error().movedFromInt); |
110 | } |
111 | |
112 | // - If has_value() is true, equivalent to: |
113 | // reinit-expected(unex, val, std::forward<GF>(e.error())); |
114 | // !is_nothrow_constructible_v<E, GF> && is_nothrow_move_constructible_v<E> |
115 | // |
116 | // In this case, it should call the branch |
117 | // T tmp(std::forward<Args>(args)...); |
118 | // destroy_at(addressof(oldval)); |
119 | // construct_at(addressof(newval), std::move(tmp)); |
120 | { |
121 | BothNoexcept::state oldState{}; |
122 | std::expected<BothNoexcept, MoveNoexceptConvThrow> e(std::in_place, oldState, 5); |
123 | std::unexpected<int> un(10); |
124 | decltype(auto) x = (e = std::move(un)); |
125 | static_assert(std::same_as<decltype(x), std::expected<BothNoexcept, MoveNoexceptConvThrow>&>); |
126 | assert(&x == &e); |
127 | |
128 | assert(!oldState.moveCtorCalled); |
129 | assert(oldState.dtorCalled); |
130 | assert(!e.error().movedFromInt); |
131 | assert(e.error().movedFromTmp); |
132 | } |
133 | |
134 | // - If has_value() is true, equivalent to: |
135 | // reinit-expected(unex, val, std::forward<GF>(e.error())); |
136 | // !is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<E> |
137 | // is_nothrow_move_constructible_v<T> |
138 | // |
139 | // In this case, it should call the branch |
140 | // U tmp(std::move(oldval)); |
141 | // destroy_at(addressof(oldval)); |
142 | // try { |
143 | // construct_at(addressof(newval), std::forward<Args>(args)...); |
144 | // } catch (...) { |
145 | // construct_at(addressof(oldval), std::move(tmp)); |
146 | // throw; |
147 | // } |
148 | { |
149 | BothNoexcept::state oldState{}; |
150 | std::expected<BothNoexcept, BothMayThrow> e(std::in_place, oldState, 5); |
151 | std::unexpected<int> un(10); |
152 | decltype(auto) x = (e = std::move(un)); |
153 | static_assert(std::same_as<decltype(x), std::expected<BothNoexcept, BothMayThrow>&>); |
154 | assert(&x == &e); |
155 | |
156 | assert(oldState.moveCtorCalled); |
157 | assert(oldState.dtorCalled); |
158 | assert(e.error().movedFromInt); |
159 | } |
160 | |
161 | // Otherwise, equivalent to: unex = std::forward<GF>(e.error()); |
162 | { |
163 | Traced::state oldState{}; |
164 | Traced::state newState{}; |
165 | std::expected<int, Traced> e1(std::unexpect, oldState, 5); |
166 | std::unexpected<Traced> e(std::in_place, newState, 10); |
167 | decltype(auto) x = (e1 = std::move(e)); |
168 | static_assert(std::same_as<decltype(x), std::expected<int, Traced >&>); |
169 | assert(&x == &e1); |
170 | |
171 | assert(!e1.has_value()); |
172 | assert(e1.error().data_ == 10); |
173 | assert(oldState.moveAssignCalled); |
174 | } |
175 | return true; |
176 | } |
177 | |
178 | void testException() { |
179 | #ifndef TEST_HAS_NO_EXCEPTIONS |
180 | std::expected<int, ThrowOnConvert> e1(std::in_place, 5); |
181 | std::unexpected<int> un(10); |
182 | try { |
183 | e1 = std::move(un); |
184 | assert(false); |
185 | } catch (Except) { |
186 | assert(e1.has_value()); |
187 | assert(*e1 == 5); |
188 | } |
189 | #endif // TEST_HAS_NO_EXCEPTIONS |
190 | } |
191 | |
192 | int main(int, char**) { |
193 | test(); |
194 | static_assert(test()); |
195 | testException(); |
196 | return 0; |
197 | } |
198 | |