LCOV - code coverage report
Current view: top level - capy/ex - any_executor.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 64 64
Test Date: 2026-10-09 22:04:58 Functions: 71.8 % 78 56 22

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3                 : // Copyright (c) 2026 Michael Vandeberg
       4                 : //
       5                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7                 : //
       8                 : // Official repository: https://github.com/cppalliance/capy
       9                 : //
      10                 : 
      11                 : #ifndef BOOST_CAPY_ANY_EXECUTOR_HPP
      12                 : #define BOOST_CAPY_ANY_EXECUTOR_HPP
      13                 : 
      14                 : #include <boost/capy/detail/config.hpp>
      15                 : #include <boost/capy/continuation.hpp>
      16                 : #include <boost/capy/detail/type_id.hpp>
      17                 : #include <concepts>
      18                 : #include <coroutine>
      19                 : #include <memory>
      20                 : #include <type_traits>
      21                 : 
      22                 : namespace boost {
      23                 : namespace capy {
      24                 : 
      25                 : class execution_context;
      26                 : template<typename> class strand;
      27                 : 
      28                 : namespace detail {
      29                 : 
      30                 : template<typename T>
      31                 : struct is_strand_type : std::false_type {};
      32                 : 
      33                 : template<typename E>
      34                 : struct is_strand_type<strand<E>> : std::true_type {};
      35                 : 
      36                 : } // detail
      37                 : 
      38                 : /** Forwards `dispatch`/`post`/`context` calls through a shared, type-erased executor pointer.
      39                 : 
      40                 :     This class provides type erasure for any executor type, enabling
      41                 :     runtime polymorphism with automatic memory management via shared
      42                 :     ownership. It stores a shared pointer to a polymorphic wrapper,
      43                 :     allowing executors of different types to be stored uniformly
      44                 :     while satisfying the full `Executor` concept.
      45                 : 
      46                 :     @par Value Semantics
      47                 : 
      48                 :     This class has value semantics with shared ownership. Copy and
      49                 :     move operations are cheap, copying the internal shared
      50                 :     pointer. Multiple `any_executor` instances may share the same
      51                 :     underlying executor. Move operations do not invalidate the
      52                 :     source; there is no moved-from state.
      53                 : 
      54                 :     @par Default State
      55                 : 
      56                 :     A default-constructed `any_executor` holds no executor.
      57                 :     `operator bool()`, `operator==`, `target_type()`, and `target()`
      58                 :     report the empty state. `context()`, `on_work_started()`,
      59                 :     `on_work_finished()`, `dispatch()`, and `post()` are undefined
      60                 :     behavior until an executor is assigned.
      61                 : 
      62                 :     @par Thread Safety
      63                 : 
      64                 :     The `any_executor` itself is thread-safe for concurrent reads.
      65                 :     Concurrent modification requires external synchronization.
      66                 :     Executor operations are safe to call concurrently if the
      67                 :     underlying executor supports it.
      68                 : 
      69                 :     @par Executor Concept
      70                 : 
      71                 :     This class satisfies the `Executor` concept, making it usable
      72                 :     anywhere a concrete executor is expected.
      73                 : 
      74                 :     @par Example
      75                 :     @par !example example
      76                 : 
      77                 : 
      78                 :     @see executor_ref, Executor
      79                 : */
      80                 : class any_executor
      81                 : {
      82                 :     struct impl_base;
      83                 : 
      84                 :     std::shared_ptr<impl_base> p_;
      85                 : 
      86                 :     struct impl_base
      87                 :     {
      88 HIT          34 :         virtual ~impl_base() = default;
      89                 :         virtual execution_context& context() const noexcept = 0;
      90                 :         virtual void on_work_started() const noexcept = 0;
      91                 :         virtual void on_work_finished() const noexcept = 0;
      92                 :         virtual std::coroutine_handle<> dispatch(continuation&) const = 0;
      93                 :         virtual void post(continuation&) const = 0;
      94                 :         virtual bool equals(impl_base const*) const noexcept = 0;
      95                 :         virtual detail::type_info const& target_type() const noexcept = 0;
      96                 :         virtual void const* target_ptr() const noexcept = 0;
      97                 :     };
      98                 : 
      99                 :     template<class Ex>
     100                 :     struct impl final : impl_base
     101                 :     {
     102                 :         Ex ex_;
     103                 : 
     104                 :         template<class Ex1>
     105              34 :         explicit impl(Ex1&& ex)
     106              34 :             : ex_(std::forward<Ex1>(ex))
     107                 :         {
     108              34 :         }
     109                 : 
     110               7 :         execution_context& context() const noexcept override
     111                 :         {
     112               7 :             return const_cast<Ex&>(ex_).context();
     113                 :         }
     114                 : 
     115               5 :         void on_work_started() const noexcept override
     116                 :         {
     117               5 :             ex_.on_work_started();
     118               5 :         }
     119                 : 
     120               5 :         void on_work_finished() const noexcept override
     121                 :         {
     122               5 :             ex_.on_work_finished();
     123               5 :         }
     124                 : 
     125               5 :         std::coroutine_handle<> dispatch(continuation& c) const override
     126                 :         {
     127               5 :             return ex_.dispatch(c);
     128                 :         }
     129                 : 
     130              16 :         void post(continuation& c) const override
     131                 :         {
     132              16 :             ex_.post(c);
     133              16 :         }
     134                 : 
     135              12 :         bool equals(impl_base const* other) const noexcept override
     136                 :         {
     137              12 :             if(target_type() != other->target_type())
     138               2 :                 return false;
     139              10 :             return ex_ == static_cast<impl const*>(other)->ex_;
     140                 :         }
     141                 : 
     142              50 :         detail::type_info const& target_type() const noexcept override
     143                 :         {
     144              50 :             return detail::type_id<Ex>();
     145                 :         }
     146                 : 
     147              15 :         void const* target_ptr() const noexcept override
     148                 :         {
     149              15 :             return &ex_;
     150                 :         }
     151                 :     };
     152                 : 
     153                 : public:
     154                 :     /** Construct a default instance.
     155                 : 
     156                 :         Constructs an empty `any_executor`. `operator bool()` reports
     157                 :         the empty state; `context()`, `on_work_started()`,
     158                 :         `on_work_finished()`, `dispatch()`, and `post()` are undefined
     159                 :         behavior until an executor is assigned.
     160                 : 
     161                 :         @par Postconditions
     162                 :         @li `!*this`
     163                 :     */
     164               2 :     any_executor() = default;
     165                 : 
     166                 :     /** Construct a copy.
     167                 : 
     168                 :         Creates a new `any_executor` sharing ownership of the
     169                 :         underlying executor with `other`.
     170                 : 
     171                 :         @param other The executor to copy.
     172                 : 
     173                 :         @par Postconditions
     174                 :         @li `*this == other`
     175                 :     */
     176              35 :     any_executor(any_executor const& other) = default;
     177                 : 
     178                 :     /** Copy assignment operator.
     179                 : 
     180                 :         Shares ownership of the underlying executor with `other`.
     181                 : 
     182                 :         @param other The executor to copy.
     183                 : 
     184                 :         @return A reference to `*this`.
     185                 : 
     186                 :         @par Postconditions
     187                 :         @li `*this == other`
     188                 :     */
     189               6 :     any_executor& operator=(any_executor const& other) = default;
     190                 : 
     191                 :     /** Constructs from any executor type.
     192                 : 
     193                 :         Allocates storage for a copy of the given executor and
     194                 :         stores it internally. The executor must satisfy the
     195                 :         `Executor` concept.
     196                 : 
     197                 :         @param ex The executor to wrap. A copy is stored internally.
     198                 : 
     199                 :         @par Postconditions
     200                 :         @li `*this` is valid
     201                 :     */
     202                 :     template<class Ex>
     203                 :         requires (
     204                 :             !std::same_as<std::decay_t<Ex>, any_executor> &&
     205                 :             !detail::is_strand_type<std::decay_t<Ex>>::value &&
     206                 :             std::copy_constructible<std::decay_t<Ex>>)
     207              34 :     any_executor(Ex&& ex)
     208              34 :         : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
     209                 :     {
     210              34 :     }
     211                 : 
     212                 :     /** Returns true if this instance holds a valid executor.
     213                 : 
     214                 :         @return `true` if constructed with an executor, `false` if
     215                 :                 default-constructed.
     216                 :     */
     217               6 :     explicit operator bool() const noexcept
     218                 :     {
     219               6 :         return p_ != nullptr;
     220                 :     }
     221                 : 
     222                 :     /** Returns a reference to the associated execution context.
     223                 : 
     224                 :         @return A reference to the execution context.
     225                 : 
     226                 :         @pre This instance holds a valid executor.
     227                 :     */
     228               7 :     execution_context& context() const noexcept
     229                 :     {
     230               7 :         return p_->context();
     231                 :     }
     232                 : 
     233                 :     /** Informs the executor that work is beginning.
     234                 : 
     235                 :         Must be paired with a subsequent call to `on_work_finished()`.
     236                 : 
     237                 :         @pre This instance holds a valid executor.
     238                 :     */
     239               5 :     void on_work_started() const noexcept
     240                 :     {
     241               5 :         p_->on_work_started();
     242               5 :     }
     243                 : 
     244                 :     /** Informs the executor that work has completed.
     245                 : 
     246                 :         @pre A preceding call to `on_work_started()` was made.
     247                 :         @pre This instance holds a valid executor.
     248                 :     */
     249               5 :     void on_work_finished() const noexcept
     250                 :     {
     251               5 :         p_->on_work_finished();
     252               5 :     }
     253                 : 
     254                 :     /** Dispatches a continuation through the wrapped executor.
     255                 : 
     256                 :         Returns a handle for symmetric transfer. If running in the
     257                 :         executor's thread, returns `c.h`. Otherwise, posts the
     258                 :         continuation for later execution and returns
     259                 :         `std::noop_coroutine()`.
     260                 : 
     261                 :         @param c The continuation to dispatch for resumption.
     262                 :                  Must remain at a stable address until dequeued.
     263                 : 
     264                 :         @return A handle for symmetric transfer or `std::noop_coroutine()`.
     265                 : 
     266                 :         @pre This instance holds a valid executor.
     267                 :     */
     268               5 :     std::coroutine_handle<> dispatch(continuation& c) const
     269                 :     {
     270               5 :         return p_->dispatch(c);
     271                 :     }
     272                 : 
     273                 :     /** Posts a continuation to the wrapped executor.
     274                 : 
     275                 :         Posts the continuation to the executor for later execution
     276                 :         and returns. The caller should transfer to `std::noop_coroutine()`
     277                 :         after calling this.
     278                 : 
     279                 :         @param c The continuation to post for resumption.
     280                 :                  Must remain at a stable address until dequeued.
     281                 : 
     282                 :         @pre This instance holds a valid executor.
     283                 :     */
     284              16 :     void post(continuation& c) const
     285                 :     {
     286              16 :         p_->post(c);
     287              16 :     }
     288                 : 
     289                 :     /** Compares two executor wrappers for equality.
     290                 : 
     291                 :         Two `any_executor` instances are equal if they both hold
     292                 :         executors of the same type that compare equal, or if both
     293                 :         are empty.
     294                 : 
     295                 :         @param other The executor to compare against.
     296                 : 
     297                 :         @return `true` if both wrap equal executors of the same type,
     298                 :                 or both are empty.
     299                 :     */
     300              14 :     bool operator==(any_executor const& other) const noexcept
     301                 :     {
     302              14 :         if(!p_ && !other.p_)
     303               1 :             return true;
     304              13 :         if(!p_ || !other.p_)
     305               1 :             return false;
     306              12 :         return p_->equals(other.p_.get());
     307                 :     }
     308                 : 
     309                 :     /** Returns the type identity of the wrapped executor.
     310                 : 
     311                 :         With RTTI enabled, `detail::type_info` is `std::type_info`,
     312                 :         so the result compares against `typeid(Ex)`. With RTTI
     313                 :         disabled (`BOOST_CAPY_NO_RTTI`), it is capy's own type
     314                 :         identity, comparable against `detail::type_id<Ex>()`.
     315                 : 
     316                 :         @return The type identity of the stored executor type, or
     317                 :                 that of `void` if empty.
     318                 :     */
     319               9 :     detail::type_info const& target_type() const noexcept
     320                 :     {
     321               9 :         if(!p_)
     322               3 :             return detail::type_id<void>();
     323               6 :         return p_->target_type();
     324                 :     }
     325                 : 
     326                 :     /** Return a pointer to the wrapped executor if it matches
     327                 :         the requested type.
     328                 : 
     329                 :         Analogous to `std::any_cast<Ex>(&a)`. Top-level cv-qualifiers
     330                 :         on `Ex` are ignored when matching.
     331                 : 
     332                 :         Copies of an `any_executor` share one stored executor, so
     333                 :         every copy returns the same address. A modification made
     334                 :         through the non-const overload is visible to all of them.
     335                 :         Such modifications are not synchronized with concurrent use
     336                 :         of any copy.
     337                 : 
     338                 :         @tparam Ex The executor type to retrieve.
     339                 : 
     340                 :         @return A pointer to the stored executor, or `nullptr` if
     341                 :                 this instance is empty or holds a different type.
     342                 :     */
     343                 :     template<class Ex>
     344               5 :     Ex const* target() const noexcept
     345                 :     {
     346               5 :         if(!p_ || p_->target_type() != detail::type_id<Ex>())
     347               3 :             return nullptr;
     348                 :         // Casting from void const* avoids naming impl<Ex>, which would
     349                 :         // instantiate its overrides and reject non-executor types
     350               2 :         return static_cast<Ex const*>(p_->target_ptr());
     351                 :     }
     352                 : 
     353                 :     /// @copydoc target() const
     354                 :     template<class Ex>
     355              18 :     Ex* target() noexcept
     356                 :     {
     357              18 :         if(!p_ || p_->target_type() != detail::type_id<Ex>())
     358               5 :             return nullptr;
     359                 :         return const_cast<Ex*>(
     360              13 :             static_cast<Ex const*>(p_->target_ptr()));
     361                 :     }
     362                 : };
     363                 : 
     364                 : } // capy
     365                 : } // boost
     366                 : 
     367                 : #endif
        

Generated by: LCOV version 2.3