xref: /llvm-project/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp (revision e356f681f6c46ac35f933dc0cef3b25ceee8b210)
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 // template<class G>
14 //   constexpr expected& operator=(unexpected<G>&& e);
15 //
16 // Let GF be G
17 // Constraints:
18 // - is_constructible_v<E, GF> is true; and
19 // - is_assignable_v<E&, GF> is true; and
20 // - is_nothrow_constructible_v<E, GF> || is_nothrow_move_constructible_v<T> ||
21 //   is_nothrow_move_constructible_v<E> is true.
22 //
23 // Effects:
24 // - If has_value() is true, equivalent to:
25 //   reinit-expected(unex, val, std::forward<GF>(e.error()));
26 //   has_val = false;
27 // - Otherwise, equivalent to: unex = std::forward<GF>(e.error());
28 // Returns: *this.
29 
30 #include <cassert>
31 #include <concepts>
32 #include <expected>
33 #include <type_traits>
34 #include <utility>
35 
36 #include "../../types.h"
37 #include "test_macros.h"
38 
39 struct NotMoveConstructible {
40   NotMoveConstructible(NotMoveConstructible&&)            = delete;
41   NotMoveConstructible& operator=(NotMoveConstructible&&) = default;
42 };
43 
44 struct NotMoveAssignable {
45   NotMoveAssignable(NotMoveAssignable&&)            = default;
46   NotMoveAssignable& operator=(NotMoveAssignable&&) = delete;
47 };
48 
49 struct MoveMayThrow {
50   MoveMayThrow(MoveMayThrow const&)            = default;
51   MoveMayThrow& operator=(const MoveMayThrow&) = default;
52   MoveMayThrow(MoveMayThrow&&) noexcept(false) {}
53   MoveMayThrow& operator=(MoveMayThrow&&) noexcept(false) { return *this; }
54 };
55 
56 // Test constraints
57 static_assert(std::is_assignable_v<std::expected<int, int>&, std::unexpected<int>&&>);
58 
59 // !is_constructible_v<E, GF>
60 static_assert(
61     !std::is_assignable_v<std::expected<int, NotMoveConstructible>&, std::unexpected<NotMoveConstructible>&&>);
62 
63 // !is_assignable_v<E&, GF>
64 static_assert(!std::is_assignable_v<std::expected<int, NotMoveAssignable>&, std::unexpected<NotMoveAssignable>&&>);
65 
66 template <bool moveNoexcept, bool convertNoexcept>
67 struct MaybeNoexcept {
68   explicit MaybeNoexcept(int) noexcept(convertNoexcept);
69   MaybeNoexcept(MaybeNoexcept&&) noexcept(moveNoexcept);
70   MaybeNoexcept& operator=(MaybeNoexcept&&) = default;
71   MaybeNoexcept& operator=(int);
72 };
73 
74 // !is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<T> &&
75 // is_nothrow_move_constructible_v<E>
76 static_assert(std::is_assignable_v<std::expected<MaybeNoexcept<false, false>, MaybeNoexcept<true, false>>&,
77                                    std::unexpected<int>&&>);
78 
79 // is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<T> &&
80 // !is_nothrow_move_constructible_v<E>
81 static_assert(std::is_assignable_v<std::expected<MaybeNoexcept<false, false>, MaybeNoexcept<false, true>>&,
82                                    std::unexpected<int>&&>);
83 
84 // !is_nothrow_constructible_v<E, GF> && is_nothrow_move_constructible_v<T> &&
85 // !is_nothrow_move_constructible_v<E>
86 static_assert(std::is_assignable_v<std::expected<MaybeNoexcept<true, true>, MaybeNoexcept<false, false>>&,
87                                    std::unexpected<int>&&>);
88 
89 // !is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<T> &&
90 // !is_nothrow_move_constructible_v<E>
91 static_assert(!std::is_assignable_v<std::expected<MaybeNoexcept<false, false>, MaybeNoexcept<false, false>>&,
92                                     std::unexpected<int>&&>);
93 
94 constexpr bool test() {
95   // - If has_value() is true, equivalent to:
96   //   reinit-expected(unex, val, std::forward<GF>(e.error()));
97   // is_nothrow_constructible_v<E, GF>
98   //
99   //  In this case, it should call the branch
100   //    destroy_at(addressof(oldval));
101   //    construct_at(addressof(newval), std::forward<Args>(args)...);
102   {
103     BothNoexcept::state oldState{};
104     std::expected<BothNoexcept, BothNoexcept> e(std::in_place, oldState, 5);
105     std::unexpected<int> un(10);
106     decltype(auto) x = (e = std::move(un));
107     static_assert(std::same_as<decltype(x), std::expected<BothNoexcept, BothNoexcept>&>);
108     assert(&x == &e);
109 
110     assert(!oldState.moveCtorCalled);
111     assert(oldState.dtorCalled);
112     assert(e.error().movedFromInt);
113   }
114 
115   // - If has_value() is true, equivalent to:
116   //   reinit-expected(unex, val, std::forward<GF>(e.error()));
117   // !is_nothrow_constructible_v<E, GF> && is_nothrow_move_constructible_v<E>
118   //
119   //  In this case, it should call the branch
120   //  T tmp(std::forward<Args>(args)...);
121   //  destroy_at(addressof(oldval));
122   //  construct_at(addressof(newval), std::move(tmp));
123   {
124     BothNoexcept::state oldState{};
125     std::expected<BothNoexcept, MoveNoexceptConvThrow> e(std::in_place, oldState, 5);
126     std::unexpected<int> un(10);
127     decltype(auto) x = (e = std::move(un));
128     static_assert(std::same_as<decltype(x), std::expected<BothNoexcept, MoveNoexceptConvThrow>&>);
129     assert(&x == &e);
130 
131     assert(!oldState.moveCtorCalled);
132     assert(oldState.dtorCalled);
133     assert(!e.error().movedFromInt);
134     assert(e.error().movedFromTmp);
135   }
136 
137   // - If has_value() is true, equivalent to:
138   //   reinit-expected(unex, val, std::forward<GF>(e.error()));
139   // !is_nothrow_constructible_v<E, GF> && !is_nothrow_move_constructible_v<E>
140   // is_nothrow_move_constructible_v<T>
141   //
142   //  In this case, it should call the branch
143   //  U tmp(std::move(oldval));
144   //  destroy_at(addressof(oldval));
145   //  try {
146   //    construct_at(addressof(newval), std::forward<Args>(args)...);
147   //  } catch (...) {
148   //    construct_at(addressof(oldval), std::move(tmp));
149   //    throw;
150   //  }
151   {
152     BothNoexcept::state oldState{};
153     std::expected<BothNoexcept, BothMayThrow> e(std::in_place, oldState, 5);
154     std::unexpected<int> un(10);
155     decltype(auto) x = (e = std::move(un));
156     static_assert(std::same_as<decltype(x), std::expected<BothNoexcept, BothMayThrow>&>);
157     assert(&x == &e);
158 
159     assert(oldState.moveCtorCalled);
160     assert(oldState.dtorCalled);
161     assert(e.error().movedFromInt);
162   }
163 
164   // Otherwise, equivalent to: unex = std::forward<GF>(e.error());
165   {
166     Traced::state oldState{};
167     Traced::state newState{};
168     std::expected<int, Traced> e1(std::unexpect, oldState, 5);
169     std::unexpected<Traced> e(std::in_place, newState, 10);
170     decltype(auto) x = (e1 = std::move(e));
171     static_assert(std::same_as<decltype(x), std::expected<int, Traced >&>);
172     assert(&x == &e1);
173 
174     assert(!e1.has_value());
175     assert(e1.error().data_ == 10);
176     assert(oldState.moveAssignCalled);
177   }
178   return true;
179 }
180 
181 void testException() {
182 #ifndef TEST_HAS_NO_EXCEPTIONS
183   std::expected<void, ThrowOnMoveConstruct> e1(std::in_place);
184   std::unexpected<ThrowOnMoveConstruct> un(std::in_place);
185   try {
186     e1 = std::move(un);
187     assert(false);
188   } catch (Except) {
189     assert(e1.has_value());
190   }
191 #endif // TEST_HAS_NO_EXCEPTIONS
192 }
193 
194 int main(int, char**) {
195   test();
196   static_assert(test());
197   testException();
198   return 0;
199 }
200