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