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