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
40struct NotCopyConstructible {
41 NotCopyConstructible(const NotCopyConstructible&) = delete;
42 NotCopyConstructible& operator=(const NotCopyConstructible&) = default;
43};
44
45struct NotCopyAssignable {
46 NotCopyAssignable(const NotCopyAssignable&) = default;
47 NotCopyAssignable& operator=(const NotCopyAssignable&) = delete;
48};
49
50struct 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
58static_assert(std::is_copy_assignable_v<std::expected<int, int>>);
59
60// !is_copy_assignable_v<T>
61static_assert(!std::is_copy_assignable_v<std::expected<NotCopyAssignable, int>>);
62
63// !is_copy_constructible_v<T>
64static_assert(!std::is_copy_assignable_v<std::expected<NotCopyConstructible, int>>);
65
66// !is_copy_assignable_v<E>
67static_assert(!std::is_copy_assignable_v<std::expected<int, NotCopyAssignable>>);
68
69// !is_copy_constructible_v<E>
70static_assert(!std::is_copy_assignable_v<std::expected<int, NotCopyConstructible>>);
71
72// !is_nothrow_move_constructible_v<T> && is_nothrow_move_constructible_v<E>
73static_assert(std::is_copy_assignable_v<std::expected<MoveMayThrow, int>>);
74
75// is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E>
76static_assert(std::is_copy_assignable_v<std::expected<int, MoveMayThrow>>);
77
78// !is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E>
79static_assert(!std::is_copy_assignable_v<std::expected<MoveMayThrow, MoveMayThrow>>);
80
81constexpr 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
269void 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
306int main(int, char**) {
307 test();
308 static_assert(test());
309 testException();
310 return 0;
311}
312

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