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   868 void return_value(T value) 38   868 void return_value(T value)
39   { 39   {
HITCBC 40   868 result_ = std::move(value); 40   868 result_ = std::move(value);
HITCBC 41   868 } 41   868 }
42   42  
HITCBC 43   271 T&& result() noexcept 43   271 T&& result() noexcept
44   { 44   {
HITCBC 45   271 return std::move(*result_); 45   271 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   1245 void return_void() 52   1150 void return_void()
53   { 53   {
HITCBC 54   1245 } 54   1150 }
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   2750 promise_type() noexcept 125   2728 promise_type() noexcept
HITCBC 126   2750 : has_ep_(false) 126   2728 : has_ep_(false)
127   { 127   {
HITCBC 128   2750 } 128   2728 }
129   129  
130   /// Destroy the promise, releasing any stored exception. 130   /// Destroy the promise, releasing any stored exception.
HITCBC 131   2750 ~promise_type() 131   2728 ~promise_type()
132   { 132   {
HITCBC 133   2750 if(has_ep_) 133   2728 if(has_ep_)
HITCBC 134   489 ep_.~exception_ptr(); 134   489 ep_.~exception_ptr();
HITCBC 135   2750 } 135   2728 }
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   2143 std::exception_ptr exception() const noexcept 142   2048 std::exception_ptr exception() const noexcept
143   { 143   {
HITCBC 144   2143 if(has_ep_) 144   2048 if(has_ep_)
HITCBC 145   730 return ep_; 145   730 return ep_;
HITCBC 146   1413 return {}; 146   1318 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   2750 task get_return_object() 156   2728 task get_return_object()
157   { 157   {
HITCBC 158   2750 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 158   2728 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   2750 auto initial_suspend() noexcept 170   2728 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   2750 bool await_ready() const noexcept 176   2728 bool await_ready() const noexcept
177   { 177   {
HITCBC 178   2750 return false; 178   2728 return false;
179   } 179   }
180   180  
HITCBC 181   2750 void await_suspend(std::coroutine_handle<>) const noexcept 181   2728 void await_suspend(std::coroutine_handle<>) const noexcept
182   { 182   {
HITCBC 183   2750 } 183   2728 }
184   184  
HITCBC 185   2746 void await_resume() const noexcept 185   2724 void await_resume() const noexcept
186   { 186   {
187   // Restore TLS when body starts executing 187   // Restore TLS when body starts executing
HITCBC 188   2746 set_current_frame_allocator(p_->environment()->frame_allocator); 188   2724 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 189   2746 } 189   2724 }
190   }; 190   };
HITCBC 191   2750 return awaiter{this}; 191   2728 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   2602 auto final_suspend() noexcept 204   2507 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   2602 bool await_ready() const noexcept 210   2507 bool await_ready() const noexcept
211   { 211   {
HITCBC 212   2602 return false; 212   2507 return false;
213   } 213   }
214   214  
HITCBC 215   2602 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 215   2507 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
216   { 216   {
HITCBC 217   2602 return p_->continuation(); 217   2507 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   2602 return awaiter{this}; 222   2507 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   489 void unhandled_exception() noexcept 231   489 void unhandled_exception() noexcept
232   { 232   {
HITCBC 233   489 new (&ep_) std::exception_ptr(std::current_exception()); 233   489 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 234   489 has_ep_ = true; 234   489 has_ep_ = true;
HITCBC 235   489 } 235   489 }
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   2870 bool await_ready() noexcept 251   2848 bool await_ready() noexcept
252   { 252   {
HITCBC 253   2870 return a_.await_ready(); 253   2848 return a_.await_ready();
254   } 254   }
255   255  
HITCBC 256   2726 decltype(auto) await_resume() 256   2631 decltype(auto) await_resume()
257   { 257   {
258   // Restore TLS before body resumes 258   // Restore TLS before body resumes
HITCBC 259   2726 set_current_frame_allocator(p_->environment()->frame_allocator); 259   2631 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 260   2726 return a_.await_resume(); 260   2631 return a_.await_resume();
261   } 261   }
262   262  
263   template<class Promise> 263   template<class Promise>
HITCBC 264   2249 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 264   2308 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   1249 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 268   1308 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
269   else 269   else
HITCBC 270   1000 return a_.await_suspend(h, p_->environment()); 270   1000 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   2870 auto transform_awaitable(Awaitable&& a) 285   2848 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   4395 std::forward<Awaitable>(a), this}; 291   4351 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   1525 } 297   1503 }
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   5840 ~task() 309   5818 ~task()
310   { 310   {
HITCBC 311   5840 if(h_) 311   5818 if(h_)
HITCBC 312   767 h_.destroy(); 312   767 h_.destroy();
HITCBC 313   5840 } 313   5818 }
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   764 bool await_ready() const noexcept 322   764 bool await_ready() const noexcept
323   { 323   {
HITCBC 324   764 return false; 324   764 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   763 auto await_resume() 337   763 auto await_resume()
338   { 338   {
HITCBC 339   763 if(h_.promise().has_ep_) 339   763 if(h_.promise().has_ep_)
HITCBC 340   123 std::rethrow_exception(h_.promise().ep_); 340   123 std::rethrow_exception(h_.promise().ep_);
341   if constexpr (! std::is_void_v<T>) 341   if constexpr (! std::is_void_v<T>)
HITCBC 342   595 return std::move(*h_.promise().result_); 342   595 return std::move(*h_.promise().result_);
343   else 343   else
HITCBC 344   45 return; 344   45 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   683 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) 362   683 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
363   { 363   {
HITCBC 364   683 h_.promise().set_continuation(cont); 364   683 h_.promise().set_continuation(cont);
HITCBC 365   683 h_.promise().set_environment(env); 365   683 h_.promise().set_environment(env);
HITCBC 366   683 return h_; 366   683 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   2066 std::coroutine_handle<promise_type> handle() const noexcept 380   2044 std::coroutine_handle<promise_type> handle() const noexcept
381   { 381   {
HITCBC 382   2066 return h_; 382   2044 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   1983 void release() noexcept 400   1961 void release() noexcept
401   { 401   {
HITCBC 402   1983 h_ = nullptr; 402   1961 h_ = nullptr;
HITCBC 403   1983 } 403   1961 }
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   3090 task(task&& other) noexcept 415   3090 task(task&& other) noexcept
HITCBC 416   3090 : h_(std::exchange(other.h_, nullptr)) 416   3090 : h_(std::exchange(other.h_, nullptr))
417   { 417   {
HITCBC 418   3090 } 418   3090 }
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   2750 explicit task(std::coroutine_handle<promise_type> h) 444   2728 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 445   2750 : h_(h) 445   2728 : h_(h)
446   { 446   {
HITCBC 447   2750 } 447   2728 }
448   }; 448   };
449   449  
450   } // namespace capy 450   } // namespace capy
451   } // namespace boost 451   } // namespace boost
452   452  
453   #endif 453   #endif