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