LCOV - code coverage report
Current view: top level - capy - quitter.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 95 95
Test Date: 2026-08-14 20:51:18 Functions: 98.4 % 127 125 2

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Michael Vandeberg
       3                 : //
       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)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/capy
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_CAPY_QUITTER_HPP
      11                 : #define BOOST_CAPY_QUITTER_HPP
      12                 : 
      13                 : #include <boost/capy/detail/config.hpp>
      14                 : #include <boost/capy/detail/stop_requested_exception.hpp>
      15                 : #include <boost/capy/concept/executor.hpp>
      16                 : #include <boost/capy/concept/io_awaitable.hpp>
      17                 : #include <boost/capy/ex/io_awaitable_promise_base.hpp>
      18                 : #include <boost/capy/ex/io_env.hpp>
      19                 : #include <boost/capy/ex/frame_allocator.hpp>
      20                 : #include <boost/capy/detail/await_suspend_helper.hpp>
      21                 : 
      22                 : #include <exception>
      23                 : #include <optional>
      24                 : #include <type_traits>
      25                 : #include <utility>
      26                 : 
      27                 : /* Stop-aware coroutine task.
      28                 : 
      29                 :    quitter<T> is identical to task<T> except that when the stop token
      30                 :    is triggered, the coroutine body never sees the cancellation.  The
      31                 :    promise intercepts it on resume (in transform_awaiter::await_resume)
      32                 :    and throws a sentinel exception that unwinds through RAII destructors
      33                 :    to final_suspend.  The parent sees a "stopped" completion.
      34                 : 
      35                 :    See doc/quitter.md for the full design rationale. */
      36                 : 
      37                 : namespace boost {
      38                 : namespace capy {
      39                 : 
      40                 : namespace detail {
      41                 : 
      42                 : // Reuse the same return-value storage as task<T>.
      43                 : // task_return_base is defined in task.hpp, but quitter needs its own
      44                 : // copy to avoid a header dependency on task.hpp.
      45                 : template<typename T>
      46                 : struct quitter_return_base
      47                 : {
      48                 :     std::optional<T> result_;
      49                 : 
      50 HIT          11 :     void return_value(T value)
      51                 :     {
      52              11 :         result_ = std::move(value);
      53              11 :     }
      54                 : 
      55               5 :     T&& result() noexcept
      56                 :     {
      57               5 :         return std::move(*result_);
      58                 :     }
      59                 : };
      60                 : 
      61                 : template<>
      62                 : struct quitter_return_base<void>
      63                 : {
      64               2 :     void return_void()
      65                 :     {
      66               2 :     }
      67                 : };
      68                 : 
      69                 : } // namespace detail
      70                 : 
      71                 : /** Defers a coroutine body until awaited, then unwinds it early on a stop request.
      72                 : 
      73                 :     When the stop token is triggered, the next `co_await` inside the
      74                 :     coroutine short-circuits: the body never sees the result and RAII
      75                 :     destructors run normally.  The parent observes a "stopped"
      76                 :     completion via @ref promise_type::stopped.
      77                 : 
      78                 :     Everything else — frame allocation, environment propagation,
      79                 :     symmetric transfer, move semantics — is identical to @ref task.
      80                 : 
      81                 :     @par Await-effects
      82                 : 
      83                 :     Let `q` be a `quitter<T>`. `co_await q` always suspends the awaiting
      84                 :     coroutine, then transfers control directly into the quitter's
      85                 :     coroutine body on the current thread; no executor operation is
      86                 :     posted. The quitter records the caller's environment (executor, stop
      87                 :     token, and frame allocator) by pointer rather than copying it. It
      88                 :     propagates that environment to every `co_await` inside the body.
      89                 : 
      90                 :     Unlike @ref task, the stop token is checked at every point where the
      91                 :     body would resume. Those points are before the body's first
      92                 :     statement, and again each time an awaited operation resumes it. If a
      93                 :     stop request is pending, the body is not resumed. An internal
      94                 :     sentinel exception unwinds it instead, so RAII destructors run, and
      95                 :     the coroutine completes as stopped.
      96                 : 
      97                 :     The body runs until it returns, exits via an exception, or is unwound
      98                 :     by a stop request. Control then transfers directly back to the
      99                 :     awaiting coroutine, again without an executor operation.
     100                 : 
     101                 :     @par Await-returns
     102                 :     The value the body passed to `co_return`, moved out of the quitter,
     103                 :     or nothing when `T` is `void`.
     104                 : 
     105                 :     If the body exits via an unhandled exception, that exception is
     106                 :     rethrown instead.
     107                 : 
     108                 :     If the coroutine completed as stopped, the internal sentinel
     109                 :     exception is thrown instead of await-returning. Awaiting a stopped
     110                 :     `quitter` from another `quitter` therefore stops that one too. A
     111                 :     @ref task awaiting it sees the sentinel as an unhandled exception in
     112                 :     its own body. When a quitter is started by `run_async`, a stopped
     113                 :     completion reaches the error handler as the sentinel
     114                 :     `std::exception_ptr`, not the value handler.
     115                 : 
     116                 :     @par Await-postcondition
     117                 :     The quitter's coroutine has run to completion and is suspended at its
     118                 :     final suspend point; the body's RAII destructors have run. Exactly
     119                 :     one of the following holds: the body returned a value; the body
     120                 :     exited via an exception; or `handle().promise().stopped()` returns
     121                 :     `true`. When the body returned a value, the await moved it out, so a
     122                 :     quitter must not be awaited twice.
     123                 : 
     124                 :     @par Remarks
     125                 :     Supports _IoAwaitable cancellation_.
     126                 : 
     127                 :     @tparam T The result type.  Use `quitter<>` for `quitter<void>`.
     128                 : 
     129                 :     @see task, IoRunnable, IoAwaitable
     130                 : */
     131                 : template<typename T = void>
     132                 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
     133                 :     quitter
     134                 : {
     135                 :     /** Stores `quitter<T>`'s result and unwinds the body when the stop token fires.
     136                 : 
     137                 :         This is the promise object the compiler associates with a
     138                 :         `quitter<T>` coroutine. It satisfies the coroutine promise
     139                 :         requirements and participates in the I/O awaitable protocol via
     140                 :         @ref io_awaitable_promise_base. Unlike @ref task::promise_type,
     141                 :         its `transform_awaitable` checks the stop token before each
     142                 :         awaited result reaches the body. A pending stop request throws an
     143                 :         internal sentinel exception that unwinds to a "stopped"
     144                 :         completion. It is part of the coroutine machinery and is not
     145                 :         intended to be used directly by callers.
     146                 : 
     147                 :         Result storage and `return_value`/`return_void` are provided by
     148                 :         `detail::quitter_return_base<T>`.
     149                 : 
     150                 :         @see io_awaitable_promise_base, IoRunnable
     151                 :     */
     152                 :     struct promise_type
     153                 :         : io_awaitable_promise_base<promise_type>
     154                 :         , detail::quitter_return_base<T>
     155                 :     {
     156                 :     private:
     157                 :         friend quitter;
     158                 : 
     159                 :         enum class completion { running, value, exception, stopped };
     160                 : 
     161                 :         union { std::exception_ptr ep_; };
     162                 :         completion state_;
     163                 : 
     164                 :     public:
     165                 :         /// Construct the promise in the running state.
     166              33 :         promise_type() noexcept
     167              33 :             : state_(completion::running)
     168                 :         {
     169              33 :         }
     170                 : 
     171                 :         /// Destroy the promise, releasing any stored exception.
     172              33 :         ~promise_type()
     173                 :         {
     174              33 :             if(state_ == completion::exception ||
     175              29 :                state_ == completion::stopped)
     176              20 :                 ep_.~exception_ptr();
     177              33 :         }
     178                 : 
     179                 :         /** Return a non-null exception_ptr when the coroutine threw
     180                 :             or was stopped.
     181                 : 
     182                 :             Stopped quitters report the sentinel
     183                 :             stop_requested_exception so that run_async routes to
     184                 :             the error handler instead of accessing a non-existent
     185                 :             result.
     186                 : 
     187                 :             @return The stored exception if the coroutine exited via an
     188                 :             exception or was stopped, otherwise a null
     189                 :             `std::exception_ptr`.
     190                 :         */
     191              26 :         std::exception_ptr exception() const noexcept
     192                 :         {
     193              26 :             if(state_ == completion::exception ||
     194              20 :                state_ == completion::stopped)
     195              20 :                 return ep_;
     196               6 :             return {};
     197                 :         }
     198                 : 
     199                 :         /** True when the coroutine was stopped via the stop token.
     200                 : 
     201                 :             @return `true` if the body was unwound by a stop request;
     202                 :             `false` if it returned a value or exited via any other
     203                 :             exception.
     204                 :         */
     205              12 :         bool stopped() const noexcept
     206                 :         {
     207              12 :             return state_ == completion::stopped;
     208                 :         }
     209                 : 
     210                 :         /** Return the owning `quitter` for this coroutine.
     211                 : 
     212                 :             Called by the compiler to produce the object returned to the
     213                 :             caller when the coroutine is created.
     214                 : 
     215                 :             @return A `quitter` owning the coroutine frame.
     216                 :         */
     217              33 :         quitter get_return_object()
     218                 :         {
     219                 :             return quitter{
     220              33 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     221                 :         }
     222                 : 
     223                 :         /** Return the initial-suspend awaiter.
     224                 : 
     225                 :             The coroutine always suspends at the initial suspend point,
     226                 :             so the body does not start until the quitter is awaited. When
     227                 :             the body is resumed, the awaiter restores the thread-local
     228                 :             frame allocator. It then throws the internal sentinel
     229                 :             exception if stop is already requested, so the body never
     230                 :             runs and the coroutine completes as stopped.
     231                 : 
     232                 :             @return An awaiter that suspends unconditionally.
     233                 :         */
     234              33 :         auto initial_suspend() noexcept
     235                 :         {
     236                 :             struct awaiter
     237                 :             {
     238                 :                 promise_type* p_;
     239                 : 
     240              33 :                 bool await_ready() const noexcept
     241                 :                 {
     242              33 :                     return false;
     243                 :                 }
     244                 : 
     245              33 :                 void await_suspend(std::coroutine_handle<>) const noexcept
     246                 :                 {
     247              33 :                 }
     248                 : 
     249                 :                 // Potentially-throwing: checks the stop token before
     250                 :                 // the coroutine body executes its first statement.
     251              33 :                 void await_resume() const
     252                 :                 {
     253              33 :                     set_current_frame_allocator(
     254              33 :                         p_->environment()->frame_allocator);
     255              33 :                     if(p_->environment()->stop_token.stop_requested())
     256               2 :                         throw detail::stop_requested_exception{};
     257              31 :                 }
     258                 :             };
     259              33 :             return awaiter{this};
     260                 :         }
     261                 : 
     262                 :         /** Return the final-suspend awaiter.
     263                 : 
     264                 :             The coroutine always suspends at the final suspend point. The
     265                 :             awaiter's `await_suspend` performs symmetric transfer to the
     266                 :             stored continuation, resuming the awaiting coroutine.
     267                 : 
     268                 :             @return An awaiter that suspends and transfers to the
     269                 :             continuation.
     270                 :         */
     271              33 :         auto final_suspend() noexcept
     272                 :         {
     273                 :             struct awaiter
     274                 :             {
     275                 :                 promise_type* p_;
     276                 : 
     277              33 :                 bool await_ready() const noexcept
     278                 :                 {
     279              33 :                     return false;
     280                 :                 }
     281                 : 
     282              33 :                 std::coroutine_handle<> await_suspend(
     283                 :                     std::coroutine_handle<>) const noexcept
     284                 :                 {
     285              33 :                     return p_->continuation();
     286                 :                 }
     287                 : 
     288                 :                 void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
     289                 :             };
     290              33 :             return awaiter{this};
     291                 :         }
     292                 : 
     293                 :         /** Capture the in-flight exception from the coroutine body.
     294                 : 
     295                 :             Called by the compiler when the coroutine body exits via an
     296                 :             unhandled exception. The internal stop sentinel is recorded as
     297                 :             a stopped completion; any other exception is recorded as an
     298                 :             exception completion. The stored exception is surfaced (or
     299                 :             routed to the error handler) when the quitter is awaited or run.
     300                 :         */
     301              20 :         void unhandled_exception()
     302                 :         {
     303                 :             try
     304                 :             {
     305              20 :                 throw;
     306                 :             }
     307              20 :             catch(detail::stop_requested_exception const&)
     308                 :             {
     309                 :                 // Store the exception_ptr so that run_async's
     310                 :                 // invoke_impl routes to the error handler
     311                 :                 // instead of accessing a non-existent result.
     312              16 :                 new (&ep_) std::exception_ptr(
     313                 :                     std::current_exception());
     314              16 :                 state_ = completion::stopped;
     315                 :             }
     316               4 :             catch(...)
     317                 :             {
     318               4 :                 new (&ep_) std::exception_ptr(
     319                 :                     std::current_exception());
     320               4 :                 state_ = completion::exception;
     321                 :             }
     322              20 :         }
     323                 : 
     324                 :         //------------------------------------------------------
     325                 :         // transform_awaitable — the key difference from task<T>
     326                 :         //------------------------------------------------------
     327                 : 
     328                 :         /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
     329                 : 
     330                 :             Forwards the environment to the inner awaitable's
     331                 :             environment-taking `await_suspend` and restores the
     332                 :             thread-local frame allocator before the body resumes. Unlike
     333                 :             `task`'s, it also checks the stop token on resumption. A
     334                 :             pending stop request throws the internal sentinel, so the body
     335                 :             unwinds before it observes the I/O result.
     336                 : 
     337                 :             @tparam Awaitable The awaitable being transformed.
     338                 :         */
     339                 :         template<class Awaitable>
     340                 :         struct transform_awaiter
     341                 :         {
     342                 :             /// The wrapped awaitable, decayed and stored by value.
     343                 :             std::decay_t<Awaitable> a_;
     344                 : 
     345                 :             /// The promise of the coroutine performing the `co_await`.
     346                 :             promise_type* p_;
     347                 : 
     348                 :             /** Report whether the wrapped awaitable is already complete.
     349                 : 
     350                 :                 The stop token is not checked here. A stop request that
     351                 :                 arrives before an already-complete operation is observed by
     352                 :                 @ref await_resume, which runs in either case.
     353                 : 
     354                 :                 @return The wrapped awaitable's own `await_ready` result:
     355                 :                 `true` if no suspension is needed.
     356                 :             */
     357              22 :             bool await_ready() noexcept
     358                 :             {
     359              22 :                 return a_.await_ready();
     360                 :             }
     361                 : 
     362                 :             /** Restore the frame allocator, check for stop, then resume the
     363                 :                 wrapped awaitable.
     364                 : 
     365                 :                 Reinstalls the thread-local frame allocator from the stored
     366                 :                 environment, then reads the environment's stop token. If a
     367                 :                 stop request is pending, the internal sentinel exception is
     368                 :                 thrown from here. The body therefore never observes the
     369                 :                 operation's result. It unwinds through its RAII destructors
     370                 :                 to a stopped completion. This is the one place `quitter`
     371                 :                 differs from @ref task::promise_type::transform_awaiter.
     372                 : 
     373                 :                 @return The wrapped awaitable's await-result, forwarded
     374                 :                 unchanged, when no stop request is pending.
     375                 : 
     376                 :                 @par Exception Safety
     377                 :                 Throws the library's internal stop sentinel if the
     378                 :                 environment's stop token has a stop request pending. The
     379                 :                 wrapped awaitable's `await_resume` is not called in that
     380                 :                 case.
     381                 :             */
     382                 :             // Check the stop token BEFORE the coroutine body
     383                 :             // sees the result of the I/O operation.
     384              22 :             decltype(auto) await_resume()
     385                 :             {
     386              22 :                 set_current_frame_allocator(
     387              22 :                     p_->environment()->frame_allocator);
     388              22 :                 if(p_->environment()->stop_token.stop_requested())
     389              14 :                     throw detail::stop_requested_exception{};
     390               8 :                 return a_.await_resume();
     391                 :             }
     392                 : 
     393                 :             /** Suspend by calling the wrapped awaitable with the
     394                 :                 environment.
     395                 : 
     396                 :                 This is the plain `await_suspend` the compiler calls for the
     397                 :                 nested `co_await`. It forwards to the wrapped awaitable's
     398                 :                 @ref IoAwaitable overload, supplying the promise's stored
     399                 :                 environment as the second argument. It then hands back
     400                 :                 that call's result unchanged, so the wrapped awaitable's
     401                 :                 suspension decision, whatever form it takes, is preserved.
     402                 :                 The stop token is not checked here; @ref await_resume checks
     403                 :                 it on the way back out.
     404                 : 
     405                 :                 @param h The coroutine performing the `co_await`.
     406                 : 
     407                 :                 @return Whatever the wrapped awaitable's `await_suspend`
     408                 :                 returns. When that is a `std::coroutine_handle<>`, the
     409                 :                 handle is routed through `detail::symmetric_transfer`.
     410                 :                 On MSVC that helper resumes the handle on the current
     411                 :                 stack, and this function returns `void`, so the awaiting
     412                 :                 coroutine suspends unconditionally. On every other
     413                 :                 compiler the handle is returned unchanged for symmetric
     414                 :                 transfer.
     415                 :             */
     416                 :             template<class Promise>
     417              21 :             auto await_suspend(
     418                 :                 std::coroutine_handle<Promise> h) noexcept
     419                 :             {
     420                 :                 using R = decltype(
     421                 :                     a_.await_suspend(h, p_->environment()));
     422                 :                 if constexpr (std::is_same_v<
     423                 :                     R, std::coroutine_handle<>>)
     424              19 :                     return detail::symmetric_transfer(
     425              38 :                         a_.await_suspend(h, p_->environment()));
     426                 :                 else
     427               2 :                     return a_.await_suspend(
     428               4 :                         h, p_->environment());
     429                 :             }
     430                 :         };
     431                 : 
     432                 :         /** Transform a nested awaitable before `co_await`.
     433                 : 
     434                 :             Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
     435                 :             coroutine's environment is propagated into it and the stop
     436                 :             token is checked on resumption. A diagnostic is emitted if the
     437                 :             awaitable does not satisfy @ref IoAwaitable.
     438                 : 
     439                 :             @param a The awaitable expression from `co_await a`.
     440                 : 
     441                 :             @return A @ref transform_awaiter wrapping `a`.
     442                 :         */
     443                 :         template<class Awaitable>
     444              22 :         auto transform_awaitable(Awaitable&& a)
     445                 :         {
     446                 :             using A = std::decay_t<Awaitable>;
     447                 :             if constexpr (IoAwaitable<A>)
     448                 :             {
     449                 :                 return transform_awaiter<Awaitable>{
     450              41 :                     std::forward<Awaitable>(a), this};
     451                 :             }
     452                 :             else
     453                 :             {
     454                 :                 static_assert(sizeof(A) == 0,
     455                 :                     "requires IoAwaitable");
     456                 :             }
     457              19 :         }
     458                 :     };
     459                 : 
     460                 :     /** Handle to the owned coroutine frame.
     461                 : 
     462                 :         Null when the quitter is empty (for example after a move or after
     463                 :         @ref release). Prefer @ref handle to read this; the member is
     464                 :         public for use by the coroutine machinery.
     465                 :     */
     466                 :     std::coroutine_handle<promise_type> h_;
     467                 : 
     468                 :     /// Destroy the quitter and its coroutine frame if owned.
     469              82 :     ~quitter()
     470                 :     {
     471              82 :         if(h_)
     472              15 :             h_.destroy();
     473              82 :     }
     474                 : 
     475                 :     /** Return false; quitters are never immediately ready.
     476                 : 
     477                 :         A quitter is lazy and has not started when it is awaited, so the
     478                 :         awaiting coroutine always suspends.
     479                 : 
     480                 :         @return `false`.
     481                 :     */
     482              15 :     bool await_ready() const noexcept
     483                 :     {
     484              15 :         return false;
     485                 :     }
     486                 : 
     487                 :     /** Return the result, rethrow exception, or propagate stop.
     488                 : 
     489                 :         When stopped, throws stop_requested_exception so that a
     490                 :         parent quitter also stops.  A parent task<T> sees this
     491                 :         as an unhandled exception — by design.
     492                 : 
     493                 :         @return The result value for non-void `T`, moved out of the
     494                 :         quitter; otherwise `void`.
     495                 : 
     496                 :         @par Exception Safety
     497                 :         If the coroutine was stopped, the library's internal stop sentinel
     498                 :         is thrown. If the body exited via any other exception, that
     499                 :         exception is rethrown.
     500                 :     */
     501              12 :     auto await_resume()
     502                 :     {
     503              12 :         if(h_.promise().stopped())
     504               6 :             throw detail::stop_requested_exception{};
     505               6 :         if(h_.promise().state_ == promise_type::completion::exception)
     506               1 :             std::rethrow_exception(h_.promise().ep_);
     507                 :         if constexpr (! std::is_void_v<T>)
     508               4 :             return std::move(*h_.promise().result_);
     509                 :         else
     510               1 :             return;
     511                 :     }
     512                 : 
     513                 :     /** Start execution with the caller's context.
     514                 : 
     515                 :         Stores `cont` as the continuation to resume on completion.
     516                 :         Stores `env` as the execution environment propagated to nested
     517                 :         `co_await` expressions. Then transfers control into the quitter's
     518                 :         coroutine body via the returned handle.
     519                 : 
     520                 :         @param cont The awaiting coroutine to resume when the quitter
     521                 :         completes.
     522                 : 
     523                 :         @param env The execution environment (executor, stop token, and
     524                 :         frame allocator). It must outlive the quitter.
     525                 : 
     526                 :         @return The quitter's coroutine handle, for symmetric transfer.
     527                 :     */
     528              15 :     std::coroutine_handle<> await_suspend(
     529                 :         std::coroutine_handle<> cont,
     530                 :         io_env const* env)
     531                 :     {
     532              15 :         h_.promise().set_continuation(cont);
     533              15 :         h_.promise().set_environment(env);
     534              15 :         return h_;
     535                 :     }
     536                 : 
     537                 :     /** Return the coroutine handle.
     538                 : 
     539                 :         @note Do not call `destroy()` on the returned handle while
     540                 :         the quitter is being awaited. The quitter's lifetime is
     541                 :         normally managed by `run_async`, `run`, or the awaiting
     542                 :         parent. Manually destroying a suspended quitter that another
     543                 :         coroutine is awaiting produces undefined behavior. For
     544                 :         cooperative cancellation, use `std::stop_token`.
     545                 : 
     546                 :         @return The coroutine handle.
     547                 :     */
     548              20 :     std::coroutine_handle<promise_type> handle() const noexcept
     549                 :     {
     550              20 :         return h_;
     551                 :     }
     552                 : 
     553                 :     /** Release ownership of the coroutine frame.
     554                 : 
     555                 :         @note The caller may call `destroy()` on the released handle
     556                 :         only when the quitter has not started or has fully completed.
     557                 :         Destroying a suspended quitter that is being awaited produces
     558                 :         undefined behavior.
     559                 : 
     560                 :         @par Postconditions
     561                 :         `handle()` returns a null handle. Callers needing the
     562                 :         original handle must save it, via @ref handle, before
     563                 :         calling this.
     564                 :     */
     565              18 :     void release() noexcept
     566                 :     {
     567              18 :         h_ = nullptr;
     568              18 :     }
     569                 : 
     570                 :     /** Copy construction is disabled; a quitter uniquely owns its frame.
     571                 : 
     572                 :         @param other The quitter that would be copied.
     573                 :     */
     574                 :     quitter(quitter const& other) = delete;
     575                 : 
     576                 :     /** Copy assignment is disabled; a quitter uniquely owns its frame.
     577                 : 
     578                 :         @param other The quitter that would be assigned from.
     579                 : 
     580                 :         @return A reference to `*this`.
     581                 :     */
     582                 :     quitter& operator=(quitter const& other) = delete;
     583                 : 
     584                 :     /** Construct by moving, transferring ownership.
     585                 : 
     586                 :         @par Postconditions
     587                 :         `other` is empty and must not be awaited.
     588                 : 
     589                 :         @param other The quitter to move from.
     590                 :     */
     591              49 :     quitter(quitter&& other) noexcept
     592              49 :         : h_(std::exchange(other.h_, nullptr))
     593                 :     {
     594              49 :     }
     595                 : 
     596                 :     /** Assign by moving, transferring ownership.
     597                 : 
     598                 :         If this quitter already owns a coroutine frame, that frame is
     599                 :         destroyed first. Self-assignment is a no-op.
     600                 : 
     601                 :         @par Postconditions
     602                 :         `other` is empty and must not be awaited.
     603                 : 
     604                 :         @param other The quitter to move from.
     605                 : 
     606                 :         @return A reference to `*this`.
     607                 :     */
     608                 :     quitter& operator=(quitter&& other) noexcept
     609                 :     {
     610                 :         if(this != &other)
     611                 :         {
     612                 :             if(h_)
     613                 :                 h_.destroy();
     614                 :             h_ = std::exchange(other.h_, nullptr);
     615                 :         }
     616                 :         return *this;
     617                 :     }
     618                 : 
     619                 : private:
     620              33 :     explicit quitter(std::coroutine_handle<promise_type> h)
     621              33 :         : h_(h)
     622                 :     {
     623              33 :     }
     624                 : };
     625                 : 
     626                 : } // namespace capy
     627                 : } // namespace boost
     628                 : 
     629                 : #endif
        

Generated by: LCOV version 2.3