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