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