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
43struct NotMoveConstructible {
44 NotMoveConstructible(NotMoveConstructible&&) = delete;
45 NotMoveConstructible& operator=(NotMoveConstructible&&) = default;
46};
47
48struct NotMoveAssignable {
49 NotMoveAssignable(NotMoveAssignable&&) = default;
50 NotMoveAssignable& operator=(NotMoveAssignable&&) = delete;
51};
52
53struct MoveCtorMayThrow {
54 MoveCtorMayThrow(MoveCtorMayThrow&&) noexcept(false) {}
55 MoveCtorMayThrow& operator=(MoveCtorMayThrow&&) noexcept = default;
56};
57
58// Test constraints
59static_assert(std::is_move_assignable_v<std::expected<int, int>>);
60
61// !is_move_assignable_v<T>
62static_assert(!std::is_move_assignable_v<std::expected<NotMoveAssignable, int>>);
63
64// !is_move_constructible_v<T>
65static_assert(!std::is_move_assignable_v<std::expected<NotMoveConstructible, int>>);
66
67// !is_move_assignable_v<E>
68static_assert(!std::is_move_assignable_v<std::expected<int, NotMoveAssignable>>);
69
70// !is_move_constructible_v<E>
71static_assert(!std::is_move_assignable_v<std::expected<int, NotMoveConstructible>>);
72
73// !is_nothrow_move_constructible_v<T> && is_nothrow_move_constructible_v<E>
74static_assert(std::is_move_assignable_v<std::expected<MoveCtorMayThrow, int>>);
75
76// is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E>
77static_assert(std::is_move_assignable_v<std::expected<int, MoveCtorMayThrow>>);
78
79// !is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E>
80static_assert(!std::is_move_assignable_v<std::expected<MoveCtorMayThrow, MoveCtorMayThrow>>);
81
82struct MoveAssignMayThrow {
83 MoveAssignMayThrow(MoveAssignMayThrow&&) noexcept = default;
84 MoveAssignMayThrow& operator=(MoveAssignMayThrow&&) noexcept(false) { return *this; }
85};
86
87// Test noexcept
88static_assert(std::is_nothrow_move_assignable_v<std::expected<int, int>>);
89
90// !is_nothrow_move_assignable_v<T>
91static_assert(!std::is_nothrow_move_assignable_v<std::expected<MoveAssignMayThrow, int>>);
92
93// !is_nothrow_move_constructible_v<T>
94static_assert(!std::is_nothrow_move_assignable_v<std::expected<MoveCtorMayThrow, int>>);
95
96// !is_nothrow_move_assignable_v<E>
97static_assert(!std::is_nothrow_move_assignable_v<std::expected<int, MoveAssignMayThrow>>);
98
99// !is_nothrow_move_constructible_v<E>
100static_assert(!std::is_nothrow_move_assignable_v<std::expected<int, MoveCtorMayThrow>>);
101
102constexpr 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
287void 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
317int main(int, char**) {
318 test();
319 static_assert(test());
320 testException();
321 return 0;
322}
323

source code of libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp