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 | // constexpr expected& operator=(expected&& rhs) noexcept(see below); |
12 | // |
13 | // Constraints: |
14 | // - is_move_constructible_v<T> is true and |
15 | // - is_move_assignable_v<T> is true and |
16 | // - is_move_constructible_v<E> is true and |
17 | // - is_move_assignable_v<E> is true and |
18 | // - is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E> is true. |
19 | // |
20 | // Effects: |
21 | // - If this->has_value() && rhs.has_value() is true, equivalent to val = std::move(*rhs). |
22 | // - Otherwise, if this->has_value() is true, equivalent to: |
23 | // reinit-expected(unex, val, std::move(rhs.error())) |
24 | // - Otherwise, if rhs.has_value() is true, equivalent to: |
25 | // reinit-expected(val, unex, std::move(*rhs)) |
26 | // - Otherwise, equivalent to unex = std::move(rhs.error()). |
27 | // - Then, if no exception was thrown, equivalent to: has_val = rhs.has_value(); return *this; |
28 | // |
29 | // Returns: *this. |
30 | // |
31 | // Remarks: The exception specification is equivalent to: |
32 | // is_nothrow_move_assignable_v<T> && is_nothrow_move_constructible_v<T> && |
33 | // is_nothrow_move_assignable_v<E> && is_nothrow_move_constructible_v<E> |
34 | |
35 | #include <cassert> |
36 | #include <concepts> |
37 | #include <expected> |
38 | #include <type_traits> |
39 | #include <utility> |
40 | |
41 | #include "../../types.h" |
42 | #include "test_macros.h" |
43 | |
44 | struct NotMoveConstructible { |
45 | NotMoveConstructible(NotMoveConstructible&&) = delete; |
46 | NotMoveConstructible& operator=(NotMoveConstructible&&) = default; |
47 | }; |
48 | |
49 | struct NotMoveAssignable { |
50 | NotMoveAssignable(NotMoveAssignable&&) = default; |
51 | NotMoveAssignable& operator=(NotMoveAssignable&&) = delete; |
52 | }; |
53 | |
54 | struct MoveCtorMayThrow { |
55 | MoveCtorMayThrow(MoveCtorMayThrow&&) noexcept(false) {} |
56 | MoveCtorMayThrow& operator=(MoveCtorMayThrow&&) noexcept = default; |
57 | }; |
58 | |
59 | // Test constraints |
60 | static_assert(std::is_move_assignable_v<std::expected<int, int>>); |
61 | |
62 | // !is_move_assignable_v<T> |
63 | static_assert(!std::is_move_assignable_v<std::expected<NotMoveAssignable, int>>); |
64 | |
65 | // !is_move_constructible_v<T> |
66 | static_assert(!std::is_move_assignable_v<std::expected<NotMoveConstructible, int>>); |
67 | |
68 | // !is_move_assignable_v<E> |
69 | static_assert(!std::is_move_assignable_v<std::expected<int, NotMoveAssignable>>); |
70 | |
71 | // !is_move_constructible_v<E> |
72 | static_assert(!std::is_move_assignable_v<std::expected<int, NotMoveConstructible>>); |
73 | |
74 | // !is_nothrow_move_constructible_v<T> && is_nothrow_move_constructible_v<E> |
75 | static_assert(std::is_move_assignable_v<std::expected<MoveCtorMayThrow, int>>); |
76 | |
77 | // is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> |
78 | static_assert(std::is_move_assignable_v<std::expected<int, MoveCtorMayThrow>>); |
79 | |
80 | // !is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> |
81 | static_assert(!std::is_move_assignable_v<std::expected<MoveCtorMayThrow, MoveCtorMayThrow>>); |
82 | |
83 | struct MoveAssignMayThrow { |
84 | MoveAssignMayThrow(MoveAssignMayThrow&&) noexcept = default; |
85 | MoveAssignMayThrow& operator=(MoveAssignMayThrow&&) noexcept(false) { return *this; } |
86 | }; |
87 | |
88 | // Test noexcept |
89 | static_assert(std::is_nothrow_move_assignable_v<std::expected<int, int>>); |
90 | |
91 | // !is_nothrow_move_assignable_v<T> |
92 | static_assert(!std::is_nothrow_move_assignable_v<std::expected<MoveAssignMayThrow, int>>); |
93 | |
94 | // !is_nothrow_move_constructible_v<T> |
95 | static_assert(!std::is_nothrow_move_assignable_v<std::expected<MoveCtorMayThrow, int>>); |
96 | |
97 | // !is_nothrow_move_assignable_v<E> |
98 | static_assert(!std::is_nothrow_move_assignable_v<std::expected<int, MoveAssignMayThrow>>); |
99 | |
100 | // !is_nothrow_move_constructible_v<E> |
101 | static_assert(!std::is_nothrow_move_assignable_v<std::expected<int, MoveCtorMayThrow>>); |
102 | |
103 | constexpr bool test() { |
104 | // If this->has_value() && rhs.has_value() is true, equivalent to val = std::move(*rhs). |
105 | { |
106 | Traced::state oldState{}; |
107 | Traced::state newState{}; |
108 | std::expected<Traced, int> e1(std::in_place, oldState, 5); |
109 | std::expected<Traced, int> e2(std::in_place, newState, 10); |
110 | decltype(auto) x = (e1 = std::move(e2)); |
111 | static_assert(std::same_as<decltype(x), std::expected<Traced, int>&>); |
112 | assert(&x == &e1); |
113 | |
114 | assert(e1.has_value()); |
115 | assert(e1.value().data_ == 10); |
116 | assert(oldState.moveAssignCalled); |
117 | } |
118 | |
119 | // - Otherwise, if this->has_value() is true, equivalent to: |
120 | // reinit-expected(unex, val, rhs.error()) |
121 | // E move is not noexcept |
122 | // In this case, it should call the branch |
123 | // |
124 | // U tmp(std::move(oldval)); |
125 | // destroy_at(addressof(oldval)); |
126 | // try { |
127 | // construct_at(addressof(newval), std::forward<Args>(args)...); |
128 | // } catch (...) { |
129 | // construct_at(addressof(oldval), std::move(tmp)); |
130 | // throw; |
131 | // } |
132 | // |
133 | { |
134 | TracedNoexcept::state oldState{}; |
135 | Traced::state newState{}; |
136 | std::expected<TracedNoexcept, Traced> e1(std::in_place, oldState, 5); |
137 | std::expected<TracedNoexcept, Traced> e2(std::unexpect, newState, 10); |
138 | |
139 | decltype(auto) x = (e1 = std::move(e2)); |
140 | static_assert(std::same_as<decltype(x), std::expected<TracedNoexcept, Traced>&>); |
141 | assert(&x == &e1); |
142 | |
143 | assert(!e1.has_value()); |
144 | assert(e1.error().data_ == 10); |
145 | |
146 | assert(!oldState.moveAssignCalled); |
147 | assert(oldState.moveCtorCalled); |
148 | assert(oldState.dtorCalled); |
149 | assert(!oldState.copyCtorCalled); |
150 | assert(!newState.copyCtorCalled); |
151 | assert(newState.moveCtorCalled); |
152 | assert(!newState.dtorCalled); |
153 | } |
154 | |
155 | // - Otherwise, if this->has_value() is true, equivalent to: |
156 | // reinit-expected(unex, val, rhs.error()) |
157 | // E move is noexcept |
158 | // In this case, it should call the branch |
159 | // |
160 | // destroy_at(addressof(oldval)); |
161 | // construct_at(addressof(newval), std::forward<Args>(args)...); |
162 | // |
163 | { |
164 | Traced::state oldState{}; |
165 | TracedNoexcept::state newState{}; |
166 | std::expected<Traced, TracedNoexcept> e1(std::in_place, oldState, 5); |
167 | std::expected<Traced, TracedNoexcept> e2(std::unexpect, newState, 10); |
168 | |
169 | decltype(auto) x = (e1 = std::move(e2)); |
170 | static_assert(std::same_as<decltype(x), std::expected<Traced, TracedNoexcept>&>); |
171 | assert(&x == &e1); |
172 | |
173 | assert(!e1.has_value()); |
174 | assert(e1.error().data_ == 10); |
175 | |
176 | assert(!oldState.moveCtorCalled); |
177 | assert(oldState.dtorCalled); |
178 | assert(!oldState.copyCtorCalled); |
179 | assert(!newState.copyCtorCalled); |
180 | assert(newState.moveCtorCalled); |
181 | assert(!newState.dtorCalled); |
182 | } |
183 | |
184 | // - Otherwise, if rhs.has_value() is true, equivalent to: |
185 | // reinit-expected(val, unex, *rhs) |
186 | // T move is not noexcept |
187 | // In this case, it should call the branch |
188 | // |
189 | // U tmp(std::move(oldval)); |
190 | // destroy_at(addressof(oldval)); |
191 | // try { |
192 | // construct_at(addressof(newval), std::forward<Args>(args)...); |
193 | // } catch (...) { |
194 | // construct_at(addressof(oldval), std::move(tmp)); |
195 | // throw; |
196 | // } |
197 | // |
198 | { |
199 | TracedNoexcept::state oldState{}; |
200 | Traced::state newState{}; |
201 | std::expected<Traced, TracedNoexcept> e1(std::unexpect, oldState, 5); |
202 | std::expected<Traced, TracedNoexcept> e2(std::in_place, newState, 10); |
203 | |
204 | decltype(auto) x = (e1 = std::move(e2)); |
205 | static_assert(std::same_as<decltype(x), std::expected<Traced, TracedNoexcept>&>); |
206 | assert(&x == &e1); |
207 | |
208 | assert(e1.has_value()); |
209 | assert(e1.value().data_ == 10); |
210 | |
211 | assert(oldState.moveCtorCalled); |
212 | assert(oldState.dtorCalled); |
213 | assert(!oldState.copyCtorCalled); |
214 | assert(!newState.copyCtorCalled); |
215 | assert(newState.moveCtorCalled); |
216 | assert(!newState.dtorCalled); |
217 | } |
218 | |
219 | // - Otherwise, if rhs.has_value() is true, equivalent to: |
220 | // reinit-expected(val, unex, *rhs) |
221 | // T move is noexcept |
222 | // In this case, it should call the branch |
223 | // |
224 | // destroy_at(addressof(oldval)); |
225 | // construct_at(addressof(newval), std::forward<Args>(args)...); |
226 | // |
227 | { |
228 | Traced::state oldState{}; |
229 | TracedNoexcept::state newState{}; |
230 | std::expected<TracedNoexcept, Traced> e1(std::unexpect, oldState, 5); |
231 | std::expected<TracedNoexcept, Traced> e2(std::in_place, newState, 10); |
232 | |
233 | decltype(auto) x = (e1 = std::move(e2)); |
234 | static_assert(std::same_as<decltype(x), std::expected<TracedNoexcept, Traced>&>); |
235 | assert(&x == &e1); |
236 | |
237 | assert(e1.has_value()); |
238 | assert(e1.value().data_ == 10); |
239 | |
240 | assert(!oldState.moveCtorCalled); |
241 | assert(oldState.dtorCalled); |
242 | assert(!oldState.copyCtorCalled); |
243 | assert(!newState.copyCtorCalled); |
244 | assert(newState.moveCtorCalled); |
245 | assert(!newState.dtorCalled); |
246 | } |
247 | |
248 | // Otherwise, equivalent to unex = rhs.error(). |
249 | { |
250 | Traced::state oldState{}; |
251 | Traced::state newState{}; |
252 | std::expected<int, Traced> e1(std::unexpect, oldState, 5); |
253 | std::expected<int, Traced> e2(std::unexpect, newState, 10); |
254 | decltype(auto) x = (e1 = std::move(e2)); |
255 | static_assert(std::same_as<decltype(x), std::expected<int, Traced>&>); |
256 | assert(&x == &e1); |
257 | |
258 | assert(!e1.has_value()); |
259 | assert(e1.error().data_ == 10); |
260 | assert(oldState.moveAssignCalled); |
261 | } |
262 | |
263 | // CheckForInvalidWrites |
264 | { |
265 | { |
266 | CheckForInvalidWrites<true> e1(std::unexpect); |
267 | CheckForInvalidWrites<true> e2; |
268 | |
269 | e1 = std::move(e2); |
270 | |
271 | assert(e1.check()); |
272 | assert(e2.check()); |
273 | } |
274 | { |
275 | CheckForInvalidWrites<false> e1(std::unexpect); |
276 | CheckForInvalidWrites<false> e2; |
277 | |
278 | e1 = std::move(e2); |
279 | |
280 | assert(e1.check()); |
281 | assert(e2.check()); |
282 | } |
283 | } |
284 | |
285 | return true; |
286 | } |
287 | |
288 | void testException() { |
289 | #ifndef TEST_HAS_NO_EXCEPTIONS |
290 | // assign value throw on move |
291 | { |
292 | std::expected<ThrowOnMoveConstruct, int> e1(std::unexpect, 5); |
293 | std::expected<ThrowOnMoveConstruct, int> e2(std::in_place); |
294 | try { |
295 | e1 = std::move(e2); |
296 | assert(false); |
297 | } catch (Except) { |
298 | assert(!e1.has_value()); |
299 | assert(e1.error() == 5); |
300 | } |
301 | } |
302 | |
303 | // assign error throw on move |
304 | { |
305 | std::expected<int, ThrowOnMoveConstruct> e1(5); |
306 | std::expected<int, ThrowOnMoveConstruct> e2(std::unexpect); |
307 | try { |
308 | e1 = std::move(e2); |
309 | assert(false); |
310 | } catch (Except) { |
311 | assert(e1.has_value()); |
312 | assert(*e1 == 5); |
313 | } |
314 | } |
315 | #endif // TEST_HAS_NO_EXCEPTIONS |
316 | } |
317 | |
318 | int main(int, char**) { |
319 | test(); |
320 | static_assert(test()); |
321 | testException(); |
322 | return 0; |
323 | } |
324 | |