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