100.00% Lines (76/76) 100.00% Functions (27/27)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_TASK_HPP 10   #ifndef BOOST_CAPY_TASK_HPP
11   #define BOOST_CAPY_TASK_HPP 11   #define BOOST_CAPY_TASK_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/concept/executor.hpp> 14   #include <boost/capy/concept/executor.hpp>
15   #include <boost/capy/concept/io_awaitable.hpp> 15   #include <boost/capy/concept/io_awaitable.hpp>
16   #include <boost/capy/ex/io_awaitable_promise_base.hpp> 16   #include <boost/capy/ex/io_awaitable_promise_base.hpp>
17   #include <boost/capy/ex/io_env.hpp> 17   #include <boost/capy/ex/io_env.hpp>
18   #include <boost/capy/ex/frame_allocator.hpp> 18   #include <boost/capy/ex/frame_allocator.hpp>
19   #include <boost/capy/detail/await_suspend_helper.hpp> 19   #include <boost/capy/detail/await_suspend_helper.hpp>
20   20  
21   #include <exception> 21   #include <exception>
22   #include <optional> 22   #include <optional>
23   #include <type_traits> 23   #include <type_traits>
24   #include <utility> 24   #include <utility>
25   #include <variant> 25   #include <variant>
26   26  
27   namespace boost { 27   namespace boost {
28   namespace capy { 28   namespace capy {
29   29  
30   namespace detail { 30   namespace detail {
31   31  
32   // Helper base for result storage and return_void/return_value 32   // Helper base for result storage and return_void/return_value
33   template<typename T> 33   template<typename T>
34   struct task_return_base 34   struct task_return_base
35   { 35   {
36   std::optional<T> result_; 36   std::optional<T> result_;
37   37  
HITCBC 38   1184 void return_value(T value) 38   1184 void return_value(T value)
39   { 39   {
HITCBC 40   1184 result_ = std::move(value); 40   1184 result_ = std::move(value);
HITCBC 41   1184 } 41   1184 }
42   42  
HITCBC 43   152 T&& result() noexcept 43   152 T&& result() noexcept
44   { 44   {
HITCBC 45   152 return std::move(*result_); 45   152 return std::move(*result_);
46   } 46   }
47   }; 47   };
48   48  
49   template<> 49   template<>
50   struct task_return_base<void> 50   struct task_return_base<void>
51   { 51   {
HITCBC 52   1975 void return_void() 52   1943 void return_void()
53   { 53   {
HITCBC 54   1975 } 54   1943 }
55   }; 55   };
56   56  
57   } // namespace detail 57   } // namespace detail
58   58  
59   /** Lazy coroutine task satisfying @ref IoRunnable. 59   /** Lazy coroutine task satisfying @ref IoRunnable.
60   60  
61   Use `task<T>` as the return type for coroutines that perform I/O 61   Use `task<T>` as the return type for coroutines that perform I/O
62   and return a value of type `T`. The coroutine body does not start 62   and return a value of type `T`. The coroutine body does not start
63   executing until the task is awaited, enabling efficient composition 63   executing until the task is awaited, enabling efficient composition
64   without unnecessary eager execution. 64   without unnecessary eager execution.
65   65  
66   The task participates in the I/O awaitable protocol: when awaited, 66   The task participates in the I/O awaitable protocol: when awaited,
67   it receives the caller's executor and stop token, propagating them 67   it receives the caller's executor and stop token, propagating them
68   to nested `co_await` expressions. This enables cancellation and 68   to nested `co_await` expressions. This enables cancellation and
69   proper completion dispatch across executor boundaries. 69   proper completion dispatch across executor boundaries.
70   70  
71   @par Thread Safety 71   @par Thread Safety
72   Distinct objects: Safe. 72   Distinct objects: Safe.
73   Shared objects: Unsafe. 73   Shared objects: Unsafe.
74   74  
75   @par Example 75   @par Example
76   76  
77   @code 77   @code
78   task<int> compute_value() 78   task<int> compute_value()
79   { 79   {
80   auto [ec, n] = co_await stream.read_some( buf ); 80   auto [ec, n] = co_await stream.read_some( buf );
81   if( ec ) 81   if( ec )
82   co_return 0; 82   co_return 0;
83   co_return process( buf, n ); 83   co_return process( buf, n );
84   } 84   }
85   85  
86   task<> run_session( tcp_socket sock ) 86   task<> run_session( tcp_socket sock )
87   { 87   {
88   int result = co_await compute_value(); 88   int result = co_await compute_value();
89   // ... 89   // ...
90   } 90   }
91   @endcode 91   @endcode
92   92  
93   @tparam T The result type. Use `task<>` for `task<void>`. 93   @tparam T The result type. Use `task<>` for `task<void>`.
94   94  
95   @see IoRunnable, IoAwaitable, run, run_async 95   @see IoRunnable, IoAwaitable, run, run_async
96   */ 96   */
97   template<typename T = void> 97   template<typename T = void>
98   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 98   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
99   task 99   task
100   { 100   {
101   /** The coroutine promise type for `task<T>`. 101   /** The coroutine promise type for `task<T>`.
102   102  
103   This is the promise object the compiler associates with a 103   This is the promise object the compiler associates with a
104   `task<T>` coroutine. It satisfies the coroutine promise 104   `task<T>` coroutine. It satisfies the coroutine promise
105   requirements and participates in the I/O awaitable protocol via 105   requirements and participates in the I/O awaitable protocol via
106   @ref io_awaitable_promise_base. It is part of the coroutine 106   @ref io_awaitable_promise_base. It is part of the coroutine
107   machinery and is not intended to be used directly by callers. 107   machinery and is not intended to be used directly by callers.
108   108  
109   Result storage and `return_value`/`return_void` are provided by 109   Result storage and `return_value`/`return_void` are provided by
110   `detail::task_return_base<T>`. 110   `detail::task_return_base<T>`.
111   111  
112   @see io_awaitable_promise_base, IoRunnable 112   @see io_awaitable_promise_base, IoRunnable
113   */ 113   */
114   struct promise_type 114   struct promise_type
115   : io_awaitable_promise_base<promise_type> 115   : io_awaitable_promise_base<promise_type>
116   , detail::task_return_base<T> 116   , detail::task_return_base<T>
117   { 117   {
118   private: 118   private:
119   friend task; 119   friend task;
120   union { std::exception_ptr ep_; }; 120   union { std::exception_ptr ep_; };
121   bool has_ep_; 121   bool has_ep_;
122   122  
123   public: 123   public:
124   /// Construct the promise with no stored exception. 124   /// Construct the promise with no stored exception.
HITCBC 125   4679 promise_type() noexcept 125   4687 promise_type() noexcept
HITCBC 126   4679 : has_ep_(false) 126   4687 : has_ep_(false)
127   { 127   {
HITCBC 128   4679 } 128   4687 }
129   129  
130   /// Destroy the promise, releasing any stored exception. 130   /// Destroy the promise, releasing any stored exception.
HITCBC 131   4679 ~promise_type() 131   4687 ~promise_type()
132   { 132   {
HITCBC 133   4679 if(has_ep_) 133   4687 if(has_ep_)
HITCBC 134   1382 ep_.~exception_ptr(); 134   1391 ep_.~exception_ptr();
HITCBC 135   4679 } 135   4687 }
136   136  
137   /** Return the exception captured by the coroutine body, if any. 137   /** Return the exception captured by the coroutine body, if any.
138   138  
139   @return The stored exception, or a null `std::exception_ptr` 139   @return The stored exception, or a null `std::exception_ptr`
140   if the coroutine did not exit via an unhandled exception. 140   if the coroutine did not exit via an unhandled exception.
141   */ 141   */
HITCBC 142   3863 std::exception_ptr exception() const noexcept 142   3849 std::exception_ptr exception() const noexcept
143   { 143   {
HITCBC 144   3863 if(has_ep_) 144   3849 if(has_ep_)
HITCBC 145   1810 return ep_; 145   1828 return ep_;
HITCBC 146   2053 return {}; 146   2021 return {};
147   } 147   }
148   148  
149   /** Return the owning `task` for this coroutine. 149   /** Return the owning `task` for this coroutine.
150   150  
151   Called by the compiler to produce the object returned to the 151   Called by the compiler to produce the object returned to the
152   caller when the coroutine is created. 152   caller when the coroutine is created.
153   153  
154   @return A `task` owning the coroutine frame. 154   @return A `task` owning the coroutine frame.
155   */ 155   */
HITCBC 156   4679 task get_return_object() 156   4687 task get_return_object()
157   { 157   {
HITCBC 158   4679 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 158   4687 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
159   } 159   }
160   160  
161   /** Return the initial-suspend awaiter. 161   /** Return the initial-suspend awaiter.
162   162  
163   The coroutine always suspends at the initial suspend point, 163   The coroutine always suspends at the initial suspend point,
164   so the body does not start until the task is awaited. When the 164   so the body does not start until the task is awaited. When the
165   body is resumed, the awaiter restores the thread-local frame 165   body is resumed, the awaiter restores the thread-local frame
166   allocator from the stored environment. 166   allocator from the stored environment.
167   167  
168   @return An awaiter that suspends unconditionally. 168   @return An awaiter that suspends unconditionally.
169   */ 169   */
HITCBC 170   4679 auto initial_suspend() noexcept 170   4687 auto initial_suspend() noexcept
171   { 171   {
172   struct awaiter 172   struct awaiter
173   { 173   {
174   promise_type* p_; 174   promise_type* p_;
175   175  
HITCBC 176   4679 bool await_ready() const noexcept 176   4687 bool await_ready() const noexcept
177   { 177   {
HITCBC 178   4679 return false; 178   4687 return false;
179   } 179   }
180   180  
HITCBC 181   4679 void await_suspend(std::coroutine_handle<>) const noexcept 181   4687 void await_suspend(std::coroutine_handle<>) const noexcept
182   { 182   {
HITCBC 183   4679 } 183   4687 }
184   184  
HITCBC 185   4676 void await_resume() const noexcept 185   4684 void await_resume() const noexcept
186   { 186   {
187   // Restore TLS when body starts executing 187   // Restore TLS when body starts executing
HITCBC 188   4676 set_current_frame_allocator(p_->environment()->frame_allocator); 188   4684 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 189   4676 } 189   4684 }
190   }; 190   };
HITCBC 191   4679 return awaiter{this}; 191   4687 return awaiter{this};
192   } 192   }
193   193  
194   /** Return the final-suspend awaiter. 194   /** Return the final-suspend awaiter.
195   195  
196   The coroutine always suspends at the final suspend point. The 196   The coroutine always suspends at the final suspend point. The
197   awaiter's `await_suspend` performs symmetric transfer to the 197   awaiter's `await_suspend` performs symmetric transfer to the
198   stored continuation (consuming it), resuming the awaiting 198   stored continuation (consuming it), resuming the awaiting
199   coroutine. 199   coroutine.
200   200  
201   @return An awaiter that suspends and transfers to the 201   @return An awaiter that suspends and transfers to the
202   continuation. 202   continuation.
203   */ 203   */
HITCBC 204   4541 auto final_suspend() noexcept 204   4518 auto final_suspend() noexcept
205   { 205   {
206   struct awaiter 206   struct awaiter
207   { 207   {
208   promise_type* p_; 208   promise_type* p_;
209   209  
HITCBC 210   4541 bool await_ready() const noexcept 210   4518 bool await_ready() const noexcept
211   { 211   {
HITCBC 212   4541 return false; 212   4518 return false;
213   } 213   }
214   214  
HITCBC 215   4541 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 215   4518 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
216   { 216   {
HITCBC 217   4541 return p_->continuation(); 217   4518 return p_->continuation();
218   } 218   }
219   219  
220   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 220   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
221   }; 221   };
HITCBC 222   4541 return awaiter{this}; 222   4518 return awaiter{this};
223   } 223   }
224   224  
225   /** Capture the in-flight exception from the coroutine body. 225   /** Capture the in-flight exception from the coroutine body.
226   226  
227   Called by the compiler when the coroutine body exits via an 227   Called by the compiler when the coroutine body exits via an
228   unhandled exception. The captured exception is rethrown when 228   unhandled exception. The captured exception is rethrown when
229   the task is awaited. 229   the task is awaited.
230   */ 230   */
HITCBC 231   1382 void unhandled_exception() noexcept 231   1391 void unhandled_exception() noexcept
232   { 232   {
HITCBC 233   1382 new (&ep_) std::exception_ptr(std::current_exception()); 233   1391 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 234   1382 has_ep_ = true; 234   1391 has_ep_ = true;
HITCBC 235   1382 } 235   1391 }
236   236  
237   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable. 237   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
238   238  
239   Forwards the environment to the inner awaitable's 239   Forwards the environment to the inner awaitable's
240   environment-taking `await_suspend` and restores the 240   environment-taking `await_suspend` and restores the
241   thread-local frame allocator before the body resumes. 241   thread-local frame allocator before the body resumes.
242   242  
243   @tparam Awaitable The awaitable being transformed. 243   @tparam Awaitable The awaitable being transformed.
244   */ 244   */
245   template<class Awaitable> 245   template<class Awaitable>
246   struct transform_awaiter 246   struct transform_awaiter
247   { 247   {
248   std::decay_t<Awaitable> a_; 248   std::decay_t<Awaitable> a_;
249   promise_type* p_; 249   promise_type* p_;
250   250  
HITCBC 251   6800 bool await_ready() noexcept 251   6783 bool await_ready() noexcept
252   { 252   {
HITCBC 253   6800 return a_.await_ready(); 253   6783 return a_.await_ready();
254   } 254   }
255   255  
HITCBC 256   6665 decltype(auto) await_resume() 256   6617 decltype(auto) await_resume()
257   { 257   {
258   // Restore TLS before body resumes 258   // Restore TLS before body resumes
HITCBC 259   6665 set_current_frame_allocator(p_->environment()->frame_allocator); 259   6617 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 260   6665 return a_.await_resume(); 260   6617 return a_.await_resume();
261   } 261   }
262   262  
263   template<class Promise> 263   template<class Promise>
HITCBC 264   6223 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 264   6190 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
265   { 265   {
266   using R = decltype(a_.await_suspend(h, p_->environment())); 266   using R = decltype(a_.await_suspend(h, p_->environment()));
267   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 267   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 268   2734 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 268   2701 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
269   else 269   else
HITCBC 270   3489 return a_.await_suspend(h, p_->environment()); 270   3489 return a_.await_suspend(h, p_->environment());
271   } 271   }
272   }; 272   };
273   273  
274   /** Transform a nested awaitable before `co_await`. 274   /** Transform a nested awaitable before `co_await`.
275   275  
276   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the 276   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
277   coroutine's environment is propagated into it. A diagnostic 277   coroutine's environment is propagated into it. A diagnostic
278   is emitted if the awaitable does not satisfy @ref IoAwaitable. 278   is emitted if the awaitable does not satisfy @ref IoAwaitable.
279   279  
280   @param a The awaitable expression from `co_await a`. 280   @param a The awaitable expression from `co_await a`.
281   281  
282   @return A @ref transform_awaiter wrapping `a`. 282   @return A @ref transform_awaiter wrapping `a`.
283   */ 283   */
284   template<class Awaitable> 284   template<class Awaitable>
HITCBC 285   6800 auto transform_awaitable(Awaitable&& a) 285   6783 auto transform_awaitable(Awaitable&& a)
286   { 286   {
287   using A = std::decay_t<Awaitable>; 287   using A = std::decay_t<Awaitable>;
288   if constexpr (IoAwaitable<A>) 288   if constexpr (IoAwaitable<A>)
289   { 289   {
290   return transform_awaiter<Awaitable>{ 290   return transform_awaiter<Awaitable>{
HITCBC 291   9193 std::forward<Awaitable>(a), this}; 291   9159 std::forward<Awaitable>(a), this};
292   } 292   }
293   else 293   else
294   { 294   {
295   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 295   static_assert(sizeof(A) == 0, "requires IoAwaitable");
296   } 296   }
HITCBC 297   2393 } 297   2376 }
298   }; 298   };
299   299  
300   /** Handle to the owned coroutine frame. 300   /** Handle to the owned coroutine frame.
301   301  
302   Null when the task is empty (for example after a move or after 302   Null when the task is empty (for example after a move or after
303   @ref release). Prefer @ref handle to read this; the member is 303   @ref release). Prefer @ref handle to read this; the member is
304   public for use by the coroutine machinery. 304   public for use by the coroutine machinery.
305   */ 305   */
306   std::coroutine_handle<promise_type> h_; 306   std::coroutine_handle<promise_type> h_;
307   307  
308   /// Destroy the task and its coroutine frame if owned. 308   /// Destroy the task and its coroutine frame if owned.
HITCBC 309   9566 ~task() 309   9622 ~task()
310   { 310   {
HITCBC 311   9566 if(h_) 311   9622 if(h_)
HITCBC 312   1525 h_.destroy(); 312   1525 h_.destroy();
HITCBC 313   9566 } 313   9622 }
314   314  
315   /** Report whether the awaited task is already complete. 315   /** Report whether the awaited task is already complete.
316   316  
317   Always returns `false`; a task is lazy and has not started when 317   Always returns `false`; a task is lazy and has not started when
318   it is awaited, so the awaiting coroutine always suspends. 318   it is awaited, so the awaiting coroutine always suspends.
319   319  
320   @return `false`. 320   @return `false`.
321   */ 321   */
HITCBC 322   1523 bool await_ready() const noexcept 322   1523 bool await_ready() const noexcept
323   { 323   {
HITCBC 324   1523 return false; 324   1523 return false;
325   } 325   }
326   326  
327   /** Return the task's result, rethrowing any captured exception. 327   /** Return the task's result, rethrowing any captured exception.
328   328  
329   If the coroutine body exited via an unhandled exception, that 329   If the coroutine body exited via an unhandled exception, that
330   exception is rethrown here. Otherwise the result is returned by 330   exception is rethrown here. Otherwise the result is returned by
331   move (for `task<T>`) or nothing is returned (for `task<void>`). 331   move (for `task<T>`) or nothing is returned (for `task<void>`).
332   332  
333   @return The result value for non-void `T`; otherwise `void`. 333   @return The result value for non-void `T`; otherwise `void`.
334   334  
335   @throws The exception captured by the coroutine body, if any. 335   @throws The exception captured by the coroutine body, if any.
336   */ 336   */
HITCBC 337   1522 auto await_resume() 337   1522 auto await_resume()
338   { 338   {
HITCBC 339   1522 if(h_.promise().has_ep_) 339   1522 if(h_.promise().has_ep_)
HITCBC 340   476 std::rethrow_exception(h_.promise().ep_); 340   476 std::rethrow_exception(h_.promise().ep_);
341   if constexpr (! std::is_void_v<T>) 341   if constexpr (! std::is_void_v<T>)
HITCBC 342   1030 return std::move(*h_.promise().result_); 342   1030 return std::move(*h_.promise().result_);
343   else 343   else
HITCBC 344   16 return; 344   16 return;
345   } 345   }
346   346  
347   /** Start the task with the awaiting coroutine's context. 347   /** Start the task with the awaiting coroutine's context.
348   348  
349   Stores `cont` as the continuation to resume on completion and 349   Stores `cont` as the continuation to resume on completion and
350   `env` as the execution environment propagated to nested 350   `env` as the execution environment propagated to nested
351   `co_await` expressions, then transfers control into the task's 351   `co_await` expressions, then transfers control into the task's
352   coroutine body via the returned handle. 352   coroutine body via the returned handle.
353   353  
354   @param cont The awaiting coroutine to resume when the task 354   @param cont The awaiting coroutine to resume when the task
355   completes. 355   completes.
356   356  
357   @param env The execution environment (executor, stop token, and 357   @param env The execution environment (executor, stop token, and
358   frame allocator). It must outlive the task. 358   frame allocator). It must outlive the task.
359   359  
360   @return The task's coroutine handle, for symmetric transfer. 360   @return The task's coroutine handle, for symmetric transfer.
361   */ 361   */
HITCBC 362   1500 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) 362   1500 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
363   { 363   {
HITCBC 364   1500 h_.promise().set_continuation(cont); 364   1500 h_.promise().set_continuation(cont);
HITCBC 365   1500 h_.promise().set_environment(env); 365   1500 h_.promise().set_environment(env);
HITCBC 366   1500 return h_; 366   1500 return h_;
367   } 367   }
368   368  
369   /** Return the coroutine handle. 369   /** Return the coroutine handle.
370   370  
371   @note Do not call `destroy()` on the returned handle while the 371   @note Do not call `destroy()` on the returned handle while the
372   task is being awaited. The task's lifetime is normally managed 372   task is being awaited. The task's lifetime is normally managed
373   by `run_async`, `run`, or the awaiting parent; manually 373   by `run_async`, `run`, or the awaiting parent; manually
374   destroying a suspended task that another coroutine is awaiting 374   destroying a suspended task that another coroutine is awaiting
375   produces undefined behavior. For cooperative cancellation, use 375   produces undefined behavior. For cooperative cancellation, use
376   `std::stop_token`. 376   `std::stop_token`.
377   377  
378   @return The coroutine handle. 378   @return The coroutine handle.
379   */ 379   */
HITCBC 380   3179 std::coroutine_handle<promise_type> handle() const noexcept 380   3187 std::coroutine_handle<promise_type> handle() const noexcept
381   { 381   {
HITCBC 382   3179 return h_; 382   3187 return h_;
383   } 383   }
384   384  
385   /** Release ownership of the coroutine frame. 385   /** Release ownership of the coroutine frame.
386   386  
387   After calling this, destroying the task does not destroy the 387   After calling this, destroying the task does not destroy the
388   coroutine frame. The caller becomes responsible for the frame's 388   coroutine frame. The caller becomes responsible for the frame's
389   lifetime. 389   lifetime.
390   390  
391   @note If the caller intends to call `destroy()` on the 391   @note If the caller intends to call `destroy()` on the
392   released handle, it must do so only when the task has not 392   released handle, it must do so only when the task has not
393   started or has fully completed. Destroying a suspended task 393   started or has fully completed. Destroying a suspended task
394   that is being awaited produces undefined behavior. 394   that is being awaited produces undefined behavior.
395   395  
396   @par Postconditions 396   @par Postconditions
397   `handle()` returns the original handle, but the task no longer 397   `handle()` returns the original handle, but the task no longer
398   owns it. 398   owns it.
399   */ 399   */
HITCBC 400   3154 void release() noexcept 400   3162 void release() noexcept
401   { 401   {
HITCBC 402   3154 h_ = nullptr; 402   3162 h_ = nullptr;
HITCBC 403   3154 } 403   3162 }
404   404  
405   task(task const&) = delete; 405   task(task const&) = delete;
406   task& operator=(task const&) = delete; 406   task& operator=(task const&) = delete;
407   407  
408   /** Construct by moving, transferring ownership of the frame. 408   /** Construct by moving, transferring ownership of the frame.
409   409  
410   @par Postconditions 410   @par Postconditions
411   `other` is empty and must not be awaited. 411   `other` is empty and must not be awaited.
412   412  
413   @param other The task to move from. 413   @param other The task to move from.
414   */ 414   */
HITCBC 415   4887 task(task&& other) noexcept 415   4935 task(task&& other) noexcept
HITCBC 416   4887 : h_(std::exchange(other.h_, nullptr)) 416   4935 : h_(std::exchange(other.h_, nullptr))
417   { 417   {
HITCBC 418   4887 } 418   4935 }
419   419  
420   /** Assign by moving, transferring ownership of the frame. 420   /** Assign by moving, transferring ownership of the frame.
421   421  
422   If this task already owns a coroutine frame, that frame is 422   If this task already owns a coroutine frame, that frame is
423   destroyed first. Self-assignment is a no-op. 423   destroyed first. Self-assignment is a no-op.
424   424  
425   @par Postconditions 425   @par Postconditions
426   `other` is empty and must not be awaited. 426   `other` is empty and must not be awaited.
427   427  
428   @param other The task to move from. 428   @param other The task to move from.
429   429  
430   @return `*this`. 430   @return `*this`.
431   */ 431   */
432   task& operator=(task&& other) noexcept 432   task& operator=(task&& other) noexcept
433   { 433   {
434   if(this != &other) 434   if(this != &other)
435   { 435   {
436   if(h_) 436   if(h_)
437   h_.destroy(); 437   h_.destroy();
438   h_ = std::exchange(other.h_, nullptr); 438   h_ = std::exchange(other.h_, nullptr);
439   } 439   }
440   return *this; 440   return *this;
441   } 441   }
442   442  
443   private: 443   private:
HITCBC 444   4679 explicit task(std::coroutine_handle<promise_type> h) 444   4687 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 445   4679 : h_(h) 445   4687 : h_(h)
446   { 446   {
HITCBC 447   4679 } 447   4687 }
448   }; 448   };
449   449  
450   } // namespace capy 450   } // namespace capy
451   } // namespace boost 451   } // namespace boost
452   452  
453   #endif 453   #endif