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