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

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