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