LCOV - code coverage report
Current view: top level - boost/capy/test - run_blocking.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.3 % 75 73
Test Date: 2026-01-30 23:43:15 Functions: 85.9 % 185 159

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
       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_TEST_RUN_BLOCKING_HPP
      11              : #define BOOST_CAPY_TEST_RUN_BLOCKING_HPP
      12              : 
      13              : #include <boost/capy/coro.hpp>
      14              : #include <boost/capy/concept/executor.hpp>
      15              : #include <boost/capy/ex/execution_context.hpp>
      16              : #include <boost/capy/ex/run_async.hpp>
      17              : #include <boost/capy/ex/system_context.hpp>
      18              : 
      19              : #include <condition_variable>
      20              : #include <exception>
      21              : #include <mutex>
      22              : #include <stdexcept>
      23              : #include <stop_token>
      24              : #include <type_traits>
      25              : #include <utility>
      26              : 
      27              : namespace boost {
      28              : namespace capy {
      29              : namespace test {
      30              : 
      31              : /** Synchronous executor that executes inline and disallows posting.
      32              : 
      33              :     This executor executes work synchronously on the calling thread
      34              :     via `dispatch()`. Calling `post()` throws `std::logic_error`
      35              :     because posting implies deferred execution which is incompatible
      36              :     with blocking synchronous semantics.
      37              : 
      38              :     @par Thread Safety
      39              :     All member functions are thread-safe.
      40              : 
      41              :     @see run_blocking
      42              : */
      43              : struct inline_executor
      44              : {
      45              :     /// Compare two inline executors for equality.
      46              :     bool
      47            1 :     operator==(inline_executor const&) const noexcept
      48              :     {
      49            1 :         return true;
      50              :     }
      51              : 
      52              :     /** Return the associated execution context.
      53              : 
      54              :         @return A reference to a function-local static `inline_context`.
      55              :     */
      56              :     execution_context&
      57         1541 :     context() const noexcept
      58              :     {
      59              :         struct inline_context : public execution_context {};
      60         1541 :         static inline_context ctx;
      61         1541 :         return ctx;
      62              :     }
      63              : 
      64              :     /// Called when work is submitted (no-op).
      65            0 :     void on_work_started() const noexcept {}
      66              : 
      67              :     /// Called when work completes (no-op).
      68            0 :     void on_work_finished() const noexcept {}
      69              : 
      70              :     /** Dispatch work for immediate inline execution.
      71              : 
      72              :         @param h The coroutine handle to execute.
      73              : 
      74              :         @return The same handle for symmetric transfer.
      75              :     */
      76              :     coro
      77         1972 :     dispatch(coro h) const
      78              :     {
      79         1972 :         return h;
      80              :     }
      81              : 
      82              :     /** Post work for deferred execution.
      83              : 
      84              :         @par Exception Safety
      85              :         Always throws.
      86              : 
      87              :         @throws std::logic_error Always, because posting is not
      88              :             supported in a blocking context.
      89              : 
      90              :         @param h The coroutine handle (unused).
      91              :     */
      92              :     [[noreturn]] void
      93            1 :     post(coro) const
      94              :     {
      95              :         throw std::logic_error(
      96            1 :             "post not supported in blocking context");
      97              :     }
      98              : };
      99              : 
     100              : static_assert(Executor<inline_executor>);
     101              : 
     102              : //----------------------------------------------------------
     103              : //
     104              : // blocking_state
     105              : //
     106              : //----------------------------------------------------------
     107              : 
     108              : /** Synchronization state for blocking execution.
     109              : 
     110              :     Holds the mutex, condition variable, and completion flag
     111              :     used to block the caller until the task completes.
     112              : 
     113              :     @par Thread Safety
     114              :     Thread-safe when accessed under the mutex.
     115              : 
     116              :     @see run_blocking
     117              :     @see blocking_handler_wrapper
     118              : */
     119              : struct blocking_state
     120              : {
     121              :     std::mutex mtx;
     122              :     std::condition_variable cv;
     123              :     bool done = false;
     124              :     std::exception_ptr ep;
     125              : };
     126              : 
     127              : //----------------------------------------------------------
     128              : //
     129              : // blocking_handler_wrapper
     130              : //
     131              : //----------------------------------------------------------
     132              : 
     133              : /** Wrapper that signals completion after invoking the underlying handler_pair.
     134              : 
     135              :     This wrapper forwards invocations to the contained handler_pair,
     136              :     then signals the `blocking_state` condition variable so
     137              :     that `run_blocking` can unblock. Any exceptions thrown by the
     138              :     handler are captured and stored for later rethrow.
     139              : 
     140              :     @tparam H1 The success handler type.
     141              :     @tparam H2 The error handler type.
     142              : 
     143              :     @par Thread Safety
     144              :     Safe to invoke from any thread. Signals the condition
     145              :     variable after calling the handler.
     146              : 
     147              :     @see run_blocking
     148              :     @see blocking_state
     149              : */
     150              : template<class H1, class H2>
     151              : struct blocking_handler_wrapper
     152              : {
     153              :     blocking_state* state_;
     154              :     detail::handler_pair<H1, H2> handlers_;
     155              : 
     156              :     /** Invoke the handler with non-void result and signal completion. */
     157              :     template<class T>
     158           21 :     void operator()(T&& v)
     159              :     {
     160              :         try
     161              :         {
     162           21 :             handlers_(std::forward<T>(v));
     163              :         }
     164              :         catch(...)
     165              :         {
     166              :             std::lock_guard<std::mutex> lock(state_->mtx);
     167              :             state_->ep = std::current_exception();
     168              :             state_->done = true;
     169              :             state_->cv.notify_one();
     170              :             return;
     171              :         }
     172              :         {
     173           21 :             std::lock_guard<std::mutex> lock(state_->mtx);
     174           21 :             state_->done = true;
     175           21 :         }
     176           21 :         state_->cv.notify_one();
     177              :     }
     178              : 
     179              :     /** Invoke the handler for void result and signal completion. */
     180          919 :     void operator()()
     181              :     {
     182              :         try
     183              :         {
     184          919 :             handlers_();
     185              :         }
     186              :         catch(...)
     187              :         {
     188              :             std::lock_guard<std::mutex> lock(state_->mtx);
     189              :             state_->ep = std::current_exception();
     190              :             state_->done = true;
     191              :             state_->cv.notify_one();
     192              :             return;
     193              :         }
     194              :         {
     195          919 :             std::lock_guard<std::mutex> lock(state_->mtx);
     196          919 :             state_->done = true;
     197          919 :         }
     198          919 :         state_->cv.notify_one();
     199              :     }
     200              : 
     201              :     /** Invoke the handler with exception and signal completion. */
     202          602 :     void operator()(std::exception_ptr ep)
     203              :     {
     204              :         try
     205              :         {
     206         1201 :             handlers_(ep);
     207              :         }
     208         1198 :         catch(...)
     209              :         {
     210          599 :             std::lock_guard<std::mutex> lock(state_->mtx);
     211          599 :             state_->ep = std::current_exception();
     212          599 :             state_->done = true;
     213          599 :             state_->cv.notify_one();
     214          599 :             return;
     215          599 :         }
     216              :         {
     217            3 :             std::lock_guard<std::mutex> lock(state_->mtx);
     218            3 :             state_->done = true;
     219            3 :         }
     220            3 :         state_->cv.notify_one();
     221              :     }
     222              : };
     223              : 
     224              : //----------------------------------------------------------
     225              : //
     226              : // run_blocking_wrapper
     227              : //
     228              : //----------------------------------------------------------
     229              : 
     230              : /** Wrapper returned by run_blocking that accepts a task for execution.
     231              : 
     232              :     This wrapper holds the blocking state and handlers. When invoked
     233              :     with a task, it launches the task via `run_async` and blocks
     234              :     until the task completes.
     235              : 
     236              :     The rvalue ref-qualifier on `operator()` ensures the wrapper
     237              :     can only be used as a temporary.
     238              : 
     239              :     @tparam Ex The executor type satisfying the `Executor` concept.
     240              :     @tparam H1 The success handler type.
     241              :     @tparam H2 The error handler type.
     242              : 
     243              :     @par Thread Safety
     244              :     The wrapper itself should only be used from one thread.
     245              :     The calling thread blocks until the task completes.
     246              : 
     247              :     @par Example
     248              :     @code
     249              :     // Block until task completes
     250              :     int result = 0;
     251              :     run_blocking([&](int v) { result = v; })(my_task());
     252              :     @endcode
     253              : 
     254              :     @see run_blocking
     255              :     @see run_async
     256              : */
     257              : template<Executor Ex, class H1, class H2>
     258              : class [[nodiscard]] run_blocking_wrapper
     259              : {
     260              :     Ex ex_;
     261              :     std::stop_token st_;
     262              :     H1 h1_;
     263              :     H2 h2_;
     264              : 
     265              : public:
     266              :     /** Construct wrapper with executor, stop token, and handlers.
     267              : 
     268              :         @param ex The executor to execute the task on.
     269              :         @param st The stop token for cooperative cancellation.
     270              :         @param h1 The success handler.
     271              :         @param h2 The error handler.
     272              :     */
     273         1542 :     run_blocking_wrapper(
     274              :         Ex ex,
     275              :         std::stop_token st,
     276              :         H1 h1,
     277              :         H2 h2)
     278         1542 :         : ex_(std::move(ex))
     279         1542 :         , st_(std::move(st))
     280         1542 :         , h1_(std::move(h1))
     281         1542 :         , h2_(std::move(h2))
     282              :     {
     283         1542 :     }
     284              : 
     285              :     run_blocking_wrapper(run_blocking_wrapper const&) = delete;
     286              :     run_blocking_wrapper(run_blocking_wrapper&&) = delete;
     287              :     run_blocking_wrapper& operator=(run_blocking_wrapper const&) = delete;
     288              :     run_blocking_wrapper& operator=(run_blocking_wrapper&&) = delete;
     289              : 
     290              :     /** Launch the task and block until completion.
     291              : 
     292              :         This operator accepts a task, launches it via `run_async`
     293              :         with wrapped handlers, and blocks until the task completes.
     294              : 
     295              :         @tparam Task The IoLaunchableTask type.
     296              : 
     297              :         @param t The task to execute.
     298              :     */
     299              :     template<IoLaunchableTask Task>
     300              :     void
     301         1542 :     operator()(Task t) &&
     302              :     {
     303         1542 :         blocking_state state;
     304              : 
     305         3084 :         auto make_handlers = [&]() {
     306              :             if constexpr(std::is_same_v<H2, detail::default_handler>)
     307         1539 :                 return detail::handler_pair<H1, H2>{std::move(h1_)};
     308              :             else
     309            3 :                 return detail::handler_pair<H1, H2>{std::move(h1_), std::move(h2_)};
     310              :         };
     311              : 
     312              :         run_async(
     313              :             ex_,
     314         1542 :             st_,
     315         1542 :             blocking_handler_wrapper<H1, H2>{&state, make_handlers()}
     316         1542 :         )(std::move(t));
     317              : 
     318         1542 :         std::unique_lock<std::mutex> lock(state.mtx);
     319         3084 :         state.cv.wait(lock, [&] { return state.done; });
     320         1542 :         if(state.ep)
     321         1198 :             std::rethrow_exception(state.ep);
     322         2141 :     }
     323              : };
     324              : 
     325              : //----------------------------------------------------------
     326              : //
     327              : // run_blocking Overloads
     328              : //
     329              : //----------------------------------------------------------
     330              : 
     331              : // With inline_executor (default)
     332              : 
     333              : /** Block until task completes and discard result.
     334              : 
     335              :     Executes a lazy task using the inline executor and blocks the
     336              :     calling thread until the task completes or throws.
     337              : 
     338              :     @par Thread Safety
     339              :     The calling thread is blocked. The task executes inline
     340              :     on the calling thread.
     341              : 
     342              :     @par Exception Safety
     343              :     Basic guarantee. If the task throws, the exception is
     344              :     rethrown to the caller.
     345              : 
     346              :     @par Example
     347              :     @code
     348              :     run_blocking()(my_void_task());
     349              :     @endcode
     350              : 
     351              :     @return A wrapper that accepts a task for blocking execution.
     352              : 
     353              :     @see run_async
     354              : */
     355              : [[nodiscard]] inline auto
     356         1518 : run_blocking()
     357              : {
     358              :     return run_blocking_wrapper<
     359              :         inline_executor,
     360              :         detail::default_handler,
     361              :         detail::default_handler>(
     362              :             inline_executor{},
     363         3036 :             std::stop_token{},
     364              :             detail::default_handler{},
     365         1518 :             detail::default_handler{});
     366              : }
     367              : 
     368              : /** Block until task completes and invoke handler with result.
     369              : 
     370              :     Executes a lazy task using the inline executor and blocks until
     371              :     completion. The handler `h1` is called with the result on success.
     372              :     If `h1` is also invocable with `std::exception_ptr`, it handles
     373              :     exceptions too. Otherwise, exceptions are rethrown.
     374              : 
     375              :     @par Thread Safety
     376              :     The calling thread is blocked. The task and handler execute
     377              :     inline on the calling thread.
     378              : 
     379              :     @par Exception Safety
     380              :     Basic guarantee. Exceptions from the task are passed to `h1`
     381              :     if it accepts `std::exception_ptr`, otherwise rethrown.
     382              : 
     383              :     @par Example
     384              :     @code
     385              :     int result = 0;
     386              :     run_blocking([&](int v) { result = v; })(compute_value());
     387              :     @endcode
     388              : 
     389              :     @param h1 Handler invoked with the result on success, and
     390              :         optionally with `std::exception_ptr` on failure.
     391              : 
     392              :     @return A wrapper that accepts a task for blocking execution.
     393              : 
     394              :     @see run_async
     395              : */
     396              : template<class H1>
     397              : [[nodiscard]] auto
     398           18 : run_blocking(H1 h1)
     399              : {
     400              :     return run_blocking_wrapper<
     401              :         inline_executor,
     402              :         H1,
     403              :         detail::default_handler>(
     404              :             inline_executor{},
     405           36 :             std::stop_token{},
     406           18 :             std::move(h1),
     407           18 :             detail::default_handler{});
     408              : }
     409              : 
     410              : /** Block until task completes with separate handlers.
     411              : 
     412              :     Executes a lazy task using the inline executor and blocks until
     413              :     completion. The handler `h1` is called on success, `h2` on failure.
     414              : 
     415              :     @par Thread Safety
     416              :     The calling thread is blocked. The task and handlers execute
     417              :     inline on the calling thread.
     418              : 
     419              :     @par Exception Safety
     420              :     Basic guarantee. Exceptions from the task are passed to `h2`.
     421              : 
     422              :     @par Example
     423              :     @code
     424              :     int result = 0;
     425              :     run_blocking(
     426              :         [&](int v) { result = v; },
     427              :         [](std::exception_ptr ep) {
     428              :             std::rethrow_exception(ep);
     429              :         }
     430              :     )(compute_value());
     431              :     @endcode
     432              : 
     433              :     @param h1 Handler invoked with the result on success.
     434              :     @param h2 Handler invoked with the exception on failure.
     435              : 
     436              :     @return A wrapper that accepts a task for blocking execution.
     437              : 
     438              :     @see run_async
     439              : */
     440              : template<class H1, class H2>
     441              : [[nodiscard]] auto
     442            3 : run_blocking(H1 h1, H2 h2)
     443              : {
     444              :     return run_blocking_wrapper<
     445              :         inline_executor,
     446              :         H1,
     447              :         H2>(
     448              :             inline_executor{},
     449            6 :             std::stop_token{},
     450            3 :             std::move(h1),
     451            6 :             std::move(h2));
     452              : }
     453              : 
     454              : // With explicit executor
     455              : 
     456              : /** Block until task completes on the given executor.
     457              : 
     458              :     Executes a lazy task on the specified executor and blocks the
     459              :     calling thread until the task completes.
     460              : 
     461              :     @par Thread Safety
     462              :     The calling thread is blocked. The task may execute on
     463              :     a different thread depending on the executor.
     464              : 
     465              :     @par Exception Safety
     466              :     Basic guarantee. If the task throws, the exception is
     467              :     rethrown to the caller.
     468              : 
     469              :     @par Example
     470              :     @code
     471              :     run_blocking(my_executor)(my_void_task());
     472              :     @endcode
     473              : 
     474              :     @param ex The executor to execute the task on.
     475              : 
     476              :     @return A wrapper that accepts a task for blocking execution.
     477              : 
     478              :     @see run_async
     479              : */
     480              : template<Executor Ex>
     481              : [[nodiscard]] auto
     482              : run_blocking(Ex ex)
     483              : {
     484              :     return run_blocking_wrapper<
     485              :         Ex,
     486              :         detail::default_handler,
     487              :         detail::default_handler>(
     488              :             std::move(ex),
     489              :             std::stop_token{},
     490              :             detail::default_handler{},
     491              :             detail::default_handler{});
     492              : }
     493              : 
     494              : /** Block until task completes on executor with handler.
     495              : 
     496              :     Executes a lazy task on the specified executor and blocks until
     497              :     completion. The handler `h1` is called with the result.
     498              : 
     499              :     @par Thread Safety
     500              :     The calling thread is blocked. The task and handler may
     501              :     execute on a different thread depending on the executor.
     502              : 
     503              :     @par Exception Safety
     504              :     Basic guarantee. Exceptions from the task are passed to `h1`
     505              :     if it accepts `std::exception_ptr`, otherwise rethrown.
     506              : 
     507              :     @param ex The executor to execute the task on.
     508              :     @param h1 Handler invoked with the result on success.
     509              : 
     510              :     @return A wrapper that accepts a task for blocking execution.
     511              : 
     512              :     @see run_async
     513              : */
     514              : template<Executor Ex, class H1>
     515              : [[nodiscard]] auto
     516            1 : run_blocking(Ex ex, H1 h1)
     517              : {
     518              :     return run_blocking_wrapper<
     519              :         Ex,
     520              :         H1,
     521              :         detail::default_handler>(
     522            1 :             std::move(ex),
     523            2 :             std::stop_token{},
     524            1 :             std::move(h1),
     525            1 :             detail::default_handler{});
     526              : }
     527              : 
     528              : /** Block until task completes on executor with separate handlers.
     529              : 
     530              :     Executes a lazy task on the specified executor and blocks until
     531              :     completion. The handler `h1` is called on success, `h2` on failure.
     532              : 
     533              :     @par Thread Safety
     534              :     The calling thread is blocked. The task and handlers may
     535              :     execute on a different thread depending on the executor.
     536              : 
     537              :     @par Exception Safety
     538              :     Basic guarantee. Exceptions from the task are passed to `h2`.
     539              : 
     540              :     @param ex The executor to execute the task on.
     541              :     @param h1 Handler invoked with the result on success.
     542              :     @param h2 Handler invoked with the exception on failure.
     543              : 
     544              :     @return A wrapper that accepts a task for blocking execution.
     545              : 
     546              :     @see run_async
     547              : */
     548              : template<Executor Ex, class H1, class H2>
     549              : [[nodiscard]] auto
     550              : run_blocking(Ex ex, H1 h1, H2 h2)
     551              : {
     552              :     return run_blocking_wrapper<
     553              :         Ex,
     554              :         H1,
     555              :         H2>(
     556              :             std::move(ex),
     557              :             std::stop_token{},
     558              :             std::move(h1),
     559              :             std::move(h2));
     560              : }
     561              : 
     562              : // With stop_token
     563              : 
     564              : /** Block until task completes with stop token support.
     565              : 
     566              :     Executes a lazy task using the inline executor with the given
     567              :     stop token and blocks until completion.
     568              : 
     569              :     @par Thread Safety
     570              :     The calling thread is blocked. The task executes inline
     571              :     on the calling thread.
     572              : 
     573              :     @par Exception Safety
     574              :     Basic guarantee. If the task throws, the exception is
     575              :     rethrown to the caller.
     576              : 
     577              :     @param st The stop token for cooperative cancellation.
     578              : 
     579              :     @return A wrapper that accepts a task for blocking execution.
     580              : 
     581              :     @see run_async
     582              : */
     583              : [[nodiscard]] inline auto
     584              : run_blocking(std::stop_token st)
     585              : {
     586              :     return run_blocking_wrapper<
     587              :         inline_executor,
     588              :         detail::default_handler,
     589              :         detail::default_handler>(
     590              :             inline_executor{},
     591              :             std::move(st),
     592              :             detail::default_handler{},
     593              :             detail::default_handler{});
     594              : }
     595              : 
     596              : /** Block until task completes with stop token and handler.
     597              : 
     598              :     @param st The stop token for cooperative cancellation.
     599              :     @param h1 Handler invoked with the result on success.
     600              : 
     601              :     @return A wrapper that accepts a task for blocking execution.
     602              : 
     603              :     @see run_async
     604              : */
     605              : template<class H1>
     606              : [[nodiscard]] auto
     607            2 : run_blocking(std::stop_token st, H1 h1)
     608              : {
     609              :     return run_blocking_wrapper<
     610              :         inline_executor,
     611              :         H1,
     612              :         detail::default_handler>(
     613              :             inline_executor{},
     614            2 :             std::move(st),
     615            2 :             std::move(h1),
     616            2 :             detail::default_handler{});
     617              : }
     618              : 
     619              : /** Block until task completes with stop token and separate handlers.
     620              : 
     621              :     @param st The stop token for cooperative cancellation.
     622              :     @param h1 Handler invoked with the result on success.
     623              :     @param h2 Handler invoked with the exception on failure.
     624              : 
     625              :     @return A wrapper that accepts a task for blocking execution.
     626              : 
     627              :     @see run_async
     628              : */
     629              : template<class H1, class H2>
     630              : [[nodiscard]] auto
     631              : run_blocking(std::stop_token st, H1 h1, H2 h2)
     632              : {
     633              :     return run_blocking_wrapper<
     634              :         inline_executor,
     635              :         H1,
     636              :         H2>(
     637              :             inline_executor{},
     638              :             std::move(st),
     639              :             std::move(h1),
     640              :             std::move(h2));
     641              : }
     642              : 
     643              : // Executor + stop_token
     644              : 
     645              : /** Block until task completes on executor with stop token.
     646              : 
     647              :     @param ex The executor to execute the task on.
     648              :     @param st The stop token for cooperative cancellation.
     649              : 
     650              :     @return A wrapper that accepts a task for blocking execution.
     651              : 
     652              :     @see run_async
     653              : */
     654              : template<Executor Ex>
     655              : [[nodiscard]] auto
     656              : run_blocking(Ex ex, std::stop_token st)
     657              : {
     658              :     return run_blocking_wrapper<
     659              :         Ex,
     660              :         detail::default_handler,
     661              :         detail::default_handler>(
     662              :             std::move(ex),
     663              :             std::move(st),
     664              :             detail::default_handler{},
     665              :             detail::default_handler{});
     666              : }
     667              : 
     668              : /** Block until task completes on executor with stop token and handler.
     669              : 
     670              :     @param ex The executor to execute the task on.
     671              :     @param st The stop token for cooperative cancellation.
     672              :     @param h1 Handler invoked with the result on success.
     673              : 
     674              :     @return A wrapper that accepts a task for blocking execution.
     675              : 
     676              :     @see run_async
     677              : */
     678              : template<Executor Ex, class H1>
     679              : [[nodiscard]] auto
     680              : run_blocking(Ex ex, std::stop_token st, H1 h1)
     681              : {
     682              :     return run_blocking_wrapper<
     683              :         Ex,
     684              :         H1,
     685              :         detail::default_handler>(
     686              :             std::move(ex),
     687              :             std::move(st),
     688              :             std::move(h1),
     689              :             detail::default_handler{});
     690              : }
     691              : 
     692              : /** Block until task completes on executor with stop token and handlers.
     693              : 
     694              :     @param ex The executor to execute the task on.
     695              :     @param st The stop token for cooperative cancellation.
     696              :     @param h1 Handler invoked with the result on success.
     697              :     @param h2 Handler invoked with the exception on failure.
     698              : 
     699              :     @return A wrapper that accepts a task for blocking execution.
     700              : 
     701              :     @see run_async
     702              : */
     703              : template<Executor Ex, class H1, class H2>
     704              : [[nodiscard]] auto
     705              : run_blocking(Ex ex, std::stop_token st, H1 h1, H2 h2)
     706              : {
     707              :     return run_blocking_wrapper<
     708              :         Ex,
     709              :         H1,
     710              :         H2>(
     711              :             std::move(ex),
     712              :             std::move(st),
     713              :             std::move(h1),
     714              :             std::move(h2));
     715              : }
     716              : 
     717              : } // namespace test
     718              : } // namespace capy
     719              : } // namespace boost
     720              : 
     721              : #endif
        

Generated by: LCOV version 2.3