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   1188 void return_value(T value)
39   { 39   {
HITCBC 40   1184 result_ = std::move(value); 40   1188 result_ = std::move(value);
HITCBC 41   1184 } 41   1188 }
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   1969 void return_void() 52   1908 void return_void()
53   { 53   {
HITCBC 54   1969 } 54   1908 }
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   4697 promise_type() noexcept 125   4669 promise_type() noexcept
HITCBC 126   4697 : has_ep_(false) 126   4669 : has_ep_(false)
127   { 127   {
HITCBC 128   4697 } 128   4669 }
129   129  
130   /// Destroy the promise, releasing any stored exception. 130   /// Destroy the promise, releasing any stored exception.
HITCBC 131   4697 ~promise_type() 131   4669 ~promise_type()
132   { 132   {
HITCBC 133   4697 if(has_ep_) 133   4669 if(has_ep_)
HITCBC 134   1391 ep_.~exception_ptr(); 134   1386 ep_.~exception_ptr();
HITCBC 135   4697 } 135   4669 }
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   3875 std::exception_ptr exception() const noexcept 142   3800 std::exception_ptr exception() const noexcept
143   { 143   {
HITCBC 144   3875 if(has_ep_) 144   3800 if(has_ep_)
HITCBC 145   1828 return ep_; 145   1814 return ep_;
HITCBC 146   2047 return {}; 146   1986 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   4697 task get_return_object() 156   4669 task get_return_object()
157   { 157   {
HITCBC 158   4697 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 158   4669 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   4697 auto initial_suspend() noexcept 170   4669 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   4697 bool await_ready() const noexcept 176   4669 bool await_ready() const noexcept
177   { 177   {
HITCBC 178   4697 return false; 178   4669 return false;
179   } 179   }
180   180  
HITCBC 181   4697 void await_suspend(std::coroutine_handle<>) const noexcept 181   4669 void await_suspend(std::coroutine_handle<>) const noexcept
182   { 182   {
HITCBC 183   4697 } 183   4669 }
184   184  
HITCBC 185   4694 void await_resume() const noexcept 185   4666 void await_resume() const noexcept
186   { 186   {
187   // Restore TLS when body starts executing 187   // Restore TLS when body starts executing
HITCBC 188   4694 set_current_frame_allocator(p_->environment()->frame_allocator); 188   4666 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 189   4694 } 189   4666 }
190   }; 190   };
HITCBC 191   4697 return awaiter{this}; 191   4669 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   4544 auto final_suspend() noexcept 204   4482 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   4544 bool await_ready() const noexcept 210   4482 bool await_ready() const noexcept
211   { 211   {
HITCBC 212   4544 return false; 212   4482 return false;
213   } 213   }
214   214  
HITCBC 215   4544 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 215   4482 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
216   { 216   {
HITCBC 217   4544 return p_->continuation(); 217   4482 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   4544 return awaiter{this}; 222   4482 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   1391 void unhandled_exception() noexcept 231   1386 void unhandled_exception() noexcept
232   { 232   {
HITCBC 233   1391 new (&ep_) std::exception_ptr(std::current_exception()); 233   1386 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 234   1391 has_ep_ = true; 234   1386 has_ep_ = true;
HITCBC 235   1391 } 235   1386 }
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   6793 bool await_ready() noexcept 251   6790 bool await_ready() noexcept
252   { 252   {
HITCBC 253   6793 return a_.await_ready(); 253   6790 return a_.await_ready();
254   } 254   }
255   255  
HITCBC 256   6643 decltype(auto) await_resume() 256   6606 decltype(auto) await_resume()
257   { 257   {
258   // Restore TLS before body resumes 258   // Restore TLS before body resumes
HITCBC 259   6643 set_current_frame_allocator(p_->environment()->frame_allocator); 259   6606 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 260   6643 return a_.await_resume(); 260   6606 return a_.await_resume();
261   } 261   }
262   262  
263   template<class Promise> 263   template<class Promise>
HITCBC 264   6210 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 264   6228 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   2721 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 268   2739 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   6793 auto transform_awaitable(Awaitable&& a) 285   6790 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   9179 std::forward<Awaitable>(a), this}; 291   9167 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   2386 } 297   2377 }
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   9632 ~task() 309   9568 ~task()
310   { 310   {
HITCBC 311   9632 if(h_) 311   9568 if(h_)
HITCBC 312   1525 h_.destroy(); 312   1531 h_.destroy();
HITCBC 313   9632 } 313   9568 }
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   1529 bool await_ready() const noexcept
323   { 323   {
HITCBC 324   1523 return false; 324   1529 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   1528 auto await_resume()
338   { 338   {
HITCBC 339   1522 if(h_.promise().has_ep_) 339   1528 if(h_.promise().has_ep_)
HITCBC 340   476 std::rethrow_exception(h_.promise().ep_); 340   478 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   1034 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   1506 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
363   { 363   {
HITCBC 364   1500 h_.promise().set_continuation(cont); 364   1506 h_.promise().set_continuation(cont);
HITCBC 365   1500 h_.promise().set_environment(env); 365   1506 h_.promise().set_environment(env);
HITCBC 366   1500 return h_; 366   1506 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   3197 std::coroutine_handle<promise_type> handle() const noexcept 380   3163 std::coroutine_handle<promise_type> handle() const noexcept
381   { 381   {
HITCBC 382   3197 return h_; 382   3163 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   3172 void release() noexcept 400   3138 void release() noexcept
401   { 401   {
HITCBC 402   3172 h_ = nullptr; 402   3138 h_ = nullptr;
HITCBC 403   3172 } 403   3138 }
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   4935 task(task&& other) noexcept 415   4899 task(task&& other) noexcept
HITCBC 416   4935 : h_(std::exchange(other.h_, nullptr)) 416   4899 : h_(std::exchange(other.h_, nullptr))
417   { 417   {
HITCBC 418   4935 } 418   4899 }
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   4697 explicit task(std::coroutine_handle<promise_type> h) 444   4669 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 445   4697 : h_(h) 445   4669 : h_(h)
446   { 446   {
HITCBC 447   4697 } 447   4669 }
448   }; 448   };
449   449  
450   } // namespace capy 450   } // namespace capy
451   } // namespace boost 451   } // namespace boost
452   452  
453   #endif 453   #endif