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 | |