xref: /llvm-project/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp (revision 6a54dfbfe534276d644d7f9c027f0deeb748dd53)
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