100.00% Lines (46/46) 100.00% Functions (17/17)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
  3 + // Copyright (c) 2026 Michael Vandeberg
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // 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   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
8   // 9   //
9   10  
10   #ifndef BOOST_CAPY_EXECUTOR_REF_HPP 11   #ifndef BOOST_CAPY_EXECUTOR_REF_HPP
11   #define BOOST_CAPY_EXECUTOR_REF_HPP 12   #define BOOST_CAPY_EXECUTOR_REF_HPP
12   13  
13   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/detail/type_id.hpp> 15   #include <boost/capy/detail/type_id.hpp>
15   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
16   #include <concepts> 17   #include <concepts>
17   #include <coroutine> 18   #include <coroutine>
18   #include <type_traits> 19   #include <type_traits>
19   #include <utility> 20   #include <utility>
20   21  
21   namespace boost { 22   namespace boost {
22   namespace capy { 23   namespace capy {
23   24  
24   class execution_context; 25   class execution_context;
25   26  
26   namespace detail { 27   namespace detail {
27   28  
28   /** Virtual function table for type-erased executor operations. */ 29   /** Virtual function table for type-erased executor operations. */
29   struct executor_vtable 30   struct executor_vtable
30   { 31   {
31   execution_context& (*context)(void const*) noexcept; 32   execution_context& (*context)(void const*) noexcept;
32   void (*on_work_started)(void const*) noexcept; 33   void (*on_work_started)(void const*) noexcept;
33   void (*on_work_finished)(void const*) noexcept; 34   void (*on_work_finished)(void const*) noexcept;
34   void (*post)(void const*, continuation&); 35   void (*post)(void const*, continuation&);
35   std::coroutine_handle<> (*dispatch)(void const*, continuation&); 36   std::coroutine_handle<> (*dispatch)(void const*, continuation&);
36   bool (*equals)(void const*, void const*) noexcept; 37   bool (*equals)(void const*, void const*) noexcept;
37   detail::type_info const* type_id; 38   detail::type_info const* type_id;
38   }; 39   };
39   40  
40   /** Vtable instance for a specific executor type. */ 41   /** Vtable instance for a specific executor type. */
41   template<class Ex> 42   template<class Ex>
42   inline constexpr executor_vtable vtable_for = { 43   inline constexpr executor_vtable vtable_for = {
43   // context 44   // context
HITCBC 44   1 [](void const* p) noexcept -> execution_context& { 45   1 [](void const* p) noexcept -> execution_context& {
HITCBC 45   1 return const_cast<Ex*>(static_cast<Ex const*>(p))->context(); 46   1 return const_cast<Ex*>(static_cast<Ex const*>(p))->context();
46   }, 47   },
47   // on_work_started 48   // on_work_started
HITCBC 48   2 [](void const* p) noexcept { 49   2 [](void const* p) noexcept {
HITCBC 49   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started(); 50   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started();
50   }, 51   },
51   // on_work_finished 52   // on_work_finished
HITCBC 52   2 [](void const* p) noexcept { 53   2 [](void const* p) noexcept {
HITCBC 53   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_finished(); 54   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_finished();
54   }, 55   },
55   // post 56   // post
HITCBC 56   34108 [](void const* p, continuation& c) { 57   34610 [](void const* p, continuation& c) {
HITCBC 57   17054 static_cast<Ex const*>(p)->post(c); 58   17305 static_cast<Ex const*>(p)->post(c);
58   }, 59   },
59   // dispatch 60   // dispatch
HITCBC 60   122 [](void const* p, continuation& c) -> std::coroutine_handle<> { 61   122 [](void const* p, continuation& c) -> std::coroutine_handle<> {
HITCBC 61   122 return static_cast<Ex const*>(p)->dispatch(c); 62   122 return static_cast<Ex const*>(p)->dispatch(c);
62   }, 63   },
63   // equals 64   // equals
HITCBC 64   1 [](void const* a, void const* b) noexcept -> bool { 65   1 [](void const* a, void const* b) noexcept -> bool {
HITCBC 65   1 return *static_cast<Ex const*>(a) == *static_cast<Ex const*>(b); 66   1 return *static_cast<Ex const*>(a) == *static_cast<Ex const*>(b);
66   }, 67   },
67   // type_id 68   // type_id
68   &detail::type_id<Ex>() 69   &detail::type_id<Ex>()
69   }; 70   };
70   71  
71   } // detail 72   } // detail
72   73  
73 - /** A type-erased reference wrapper for executor objects. 74 + /** Forwards `dispatch`/`post`/`context` calls through a non-owning, type-erased executor pointer.
74   75  
75   This class provides type erasure for any executor type, enabling 76   This class provides type erasure for any executor type, enabling
76   runtime polymorphism without virtual functions or allocation. 77   runtime polymorphism without virtual functions or allocation.
77   It stores a pointer to the original executor and a pointer to a 78   It stores a pointer to the original executor and a pointer to a
78 - static vtable, allowing executors of different types to be stored 79 + static vtable. Executors of different types are therefore stored
79 - uniformly while satisfying the full `Executor` concept. 80 + uniformly, while satisfying the full `Executor` concept.
80   81  
81   @par Reference Semantics 82   @par Reference Semantics
82   This class has reference semantics: it does not allocate or own 83   This class has reference semantics: it does not allocate or own
83 - the wrapped executor. Copy operations simply copy the internal 84 + the wrapped executor. Copy operations copy the internal
84   pointers. The caller must ensure the referenced executor outlives 85   pointers. The caller must ensure the referenced executor outlives
85   all `executor_ref` instances that wrap it. 86   all `executor_ref` instances that wrap it.
86   87  
87   @par Thread Safety 88   @par Thread Safety
88   The `executor_ref` itself is not thread-safe for concurrent 89   The `executor_ref` itself is not thread-safe for concurrent
89   modification, but its executor operations are safe to call 90   modification, but its executor operations are safe to call
90   concurrently if the underlying executor supports it. 91   concurrently if the underlying executor supports it.
91   92  
92   @par Executor Concept 93   @par Executor Concept
93   This class satisfies the `Executor` concept, making it usable 94   This class satisfies the `Executor` concept, making it usable
94   anywhere a concrete executor is expected. 95   anywhere a concrete executor is expected.
95   96  
96   @par Example 97   @par Example
97   @code 98   @code
98   void store_executor(executor_ref ex) 99   void store_executor(executor_ref ex)
99   { 100   {
100   if(ex) 101   if(ex)
101   ex.post(my_continuation); 102   ex.post(my_continuation);
102   } 103   }
103   104  
104   thread_pool ctx; 105   thread_pool ctx;
105   store_executor(ctx.get_executor()); 106   store_executor(ctx.get_executor());
106   @endcode 107   @endcode
107   108  
108   @see any_executor, Executor 109   @see any_executor, Executor
109   */ 110   */
110   class executor_ref 111   class executor_ref
111   { 112   {
112   void const* ex_ = nullptr; 113   void const* ex_ = nullptr;
113   detail::executor_vtable const* vt_ = nullptr; 114   detail::executor_vtable const* vt_ = nullptr;
114   115  
115   public: 116   public:
116   /** Construct a default instance. 117   /** Construct a default instance.
117   118  
118 - Constructs an empty `executor_ref`. Calling any executor 119 + Constructs an empty `executor_ref`. `operator bool()` and
119 - operations on a default-constructed instance results in 120 + `operator==()` report the empty state; `context()`,
120 - undefined behavior. 121 + `on_work_started()`, `on_work_finished()`, `dispatch()`,
  122 + `post()`, and `target()` are undefined behavior until an
  123 + executor is assigned.
121   */ 124   */
HITCBC 122   3532 executor_ref() = default; 125   3528 executor_ref() = default;
123   126  
124   /** Construct a copy. 127   /** Construct a copy.
125   128  
126   Copies the internal pointers, preserving identity. 129   Copies the internal pointers, preserving identity.
127   This enables the same-executor optimization when passing 130   This enables the same-executor optimization when passing
128   executor_ref through coroutine chains. 131   executor_ref through coroutine chains.
  132 +
  133 + @param other The reference to copy.
129   */ 134   */
130 - executor_ref(executor_ref const&) = default; 135 + executor_ref(executor_ref const& other) = default;
131   136  
132 - /** Copy assignment operator. */ 137 + /** Copy assignment operator.
133 - executor_ref& operator=(executor_ref const&) = default; 138 +
  139 + @param other The reference to copy.
  140 +
  141 + @return A reference to `*this`.
  142 + */
  143 + executor_ref& operator=(executor_ref const& other) = default;
134   144  
135   /** Constructs from any executor type. 145   /** Constructs from any executor type.
136   146  
137   Captures a reference to the given executor and stores a pointer 147   Captures a reference to the given executor and stores a pointer
138   to the type-specific vtable. The executor must remain valid for 148   to the type-specific vtable. The executor must remain valid for
139   the lifetime of this `executor_ref` instance. 149   the lifetime of this `executor_ref` instance.
140   150  
141   @param ex The executor to wrap. Must satisfy the `Executor` 151   @param ex The executor to wrap. Must satisfy the `Executor`
142   concept. A pointer to this object is stored 152   concept. A pointer to this object is stored
143   internally; the executor must outlive this wrapper. 153   internally; the executor must outlive this wrapper.
144   */ 154   */
145   #if defined(__GNUC__) && !defined(__clang__) 155   #if defined(__GNUC__) && !defined(__clang__)
146   // GCC constraint satisfaction caching bug workaround 156   // GCC constraint satisfaction caching bug workaround
147   template<class Ex, 157   template<class Ex,
148   std::enable_if_t<!std::is_same_v< 158   std::enable_if_t<!std::is_same_v<
149   std::decay_t<Ex>, executor_ref>, int> = 0> 159   std::decay_t<Ex>, executor_ref>, int> = 0>
150   #else 160   #else
151   template<class Ex> 161   template<class Ex>
152   requires (!std::same_as<std::decay_t<Ex>, executor_ref>) 162   requires (!std::same_as<std::decay_t<Ex>, executor_ref>)
153   #endif 163   #endif
HITCBC 154   32427 executor_ref(Ex const& ex) noexcept 164   32430 executor_ref(Ex const& ex) noexcept
HITCBC 155   32427 : ex_(&ex) 165   32430 : ex_(&ex)
HITCBC 156   32427 , vt_(&detail::vtable_for<Ex>) 166   32430 , vt_(&detail::vtable_for<Ex>)
157   { 167   {
HITCBC 158   32427 } 168   32430 }
159   169  
160   /** Returns true if this instance holds a valid executor. 170   /** Returns true if this instance holds a valid executor.
161   171  
162   @return `true` if constructed with an executor, `false` if 172   @return `true` if constructed with an executor, `false` if
163   default-constructed. 173   default-constructed.
164   */ 174   */
HITCBC 165   6 explicit operator bool() const noexcept 175   6 explicit operator bool() const noexcept
166   { 176   {
HITCBC 167   6 return ex_ != nullptr; 177   6 return ex_ != nullptr;
168   } 178   }
169   179  
170   /** Returns a reference to the associated execution context. 180   /** Returns a reference to the associated execution context.
171   181  
172   @return A reference to the execution context. 182   @return A reference to the execution context.
173   183  
174   @pre This instance was constructed with a valid executor. 184   @pre This instance was constructed with a valid executor.
175   */ 185   */
HITCBC 176   1 execution_context& context() const noexcept 186   1 execution_context& context() const noexcept
177   { 187   {
HITCBC 178   1 return vt_->context(ex_); 188   1 return vt_->context(ex_);
179   } 189   }
180   190  
181   /** Informs the executor that work is beginning. 191   /** Informs the executor that work is beginning.
182   192  
183   Must be paired with a subsequent call to `on_work_finished()`. 193   Must be paired with a subsequent call to `on_work_finished()`.
184   194  
185   @pre This instance was constructed with a valid executor. 195   @pre This instance was constructed with a valid executor.
186   */ 196   */
HITCBC 187   1 void on_work_started() const noexcept 197   1 void on_work_started() const noexcept
188   { 198   {
HITCBC 189   1 vt_->on_work_started(ex_); 199   1 vt_->on_work_started(ex_);
HITCBC 190   1 } 200   1 }
191   201  
192   /** Informs the executor that work has completed. 202   /** Informs the executor that work has completed.
193   203  
194   @pre A preceding call to `on_work_started()` was made. 204   @pre A preceding call to `on_work_started()` was made.
195   @pre This instance was constructed with a valid executor. 205   @pre This instance was constructed with a valid executor.
196   */ 206   */
HITCBC 197   1 void on_work_finished() const noexcept 207   1 void on_work_finished() const noexcept
198   { 208   {
HITCBC 199   1 vt_->on_work_finished(ex_); 209   1 vt_->on_work_finished(ex_);
HITCBC 200   1 } 210   1 }
201   211  
202   /** Dispatches a continuation through the wrapped executor. 212   /** Dispatches a continuation through the wrapped executor.
203   213  
204   Returns a handle for symmetric transfer. If running in the 214   Returns a handle for symmetric transfer. If running in the
205   executor's thread, returns `c.h`. Otherwise, posts the 215   executor's thread, returns `c.h`. Otherwise, posts the
206   continuation for later execution and returns 216   continuation for later execution and returns
207   `std::noop_coroutine()`. 217   `std::noop_coroutine()`.
208   218  
209   @param c The continuation to dispatch for resumption. 219   @param c The continuation to dispatch for resumption.
210   Must remain at a stable address until dequeued. 220   Must remain at a stable address until dequeued.
211   221  
212   @return A handle for symmetric transfer or `std::noop_coroutine()`. 222   @return A handle for symmetric transfer or `std::noop_coroutine()`.
213   223  
214   @pre This instance was constructed with a valid executor. 224   @pre This instance was constructed with a valid executor.
215   */ 225   */
HITCBC 216   122 std::coroutine_handle<> dispatch(continuation& c) const 226   122 std::coroutine_handle<> dispatch(continuation& c) const
217   { 227   {
HITCBC 218   122 return vt_->dispatch(ex_, c); 228   122 return vt_->dispatch(ex_, c);
219   } 229   }
220   230  
221   /** Posts a continuation to the wrapped executor. 231   /** Posts a continuation to the wrapped executor.
222   232  
223   Posts the continuation to the executor for later execution 233   Posts the continuation to the executor for later execution
224   and returns. The caller should transfer to `std::noop_coroutine()` 234   and returns. The caller should transfer to `std::noop_coroutine()`
225   after calling this. 235   after calling this.
226   236  
227   @param c The continuation to post for resumption. 237   @param c The continuation to post for resumption.
228   Must remain at a stable address until dequeued. 238   Must remain at a stable address until dequeued.
229   239  
230   @pre This instance was constructed with a valid executor. 240   @pre This instance was constructed with a valid executor.
231   */ 241   */
HITCBC 232   17054 void post(continuation& c) const 242   17305 void post(continuation& c) const
233   { 243   {
HITCBC 234   17054 vt_->post(ex_, c); 244   17305 vt_->post(ex_, c);
HITCBC 235   17054 } 245   17305 }
236   246  
237   /** Compares two executor references for equality. 247   /** Compares two executor references for equality.
238   248  
239   Two `executor_ref` instances are equal if they wrap 249   Two `executor_ref` instances are equal if they wrap
240   executors of the same type that compare equal. 250   executors of the same type that compare equal.
241   251  
242   @param other The executor reference to compare against. 252   @param other The executor reference to compare against.
243   253  
244   @return `true` if both wrap equal executors of the same type. 254   @return `true` if both wrap equal executors of the same type.
245   */ 255   */
HITCBC 246   7 bool operator==(executor_ref const& other) const noexcept 256   7 bool operator==(executor_ref const& other) const noexcept
247   { 257   {
HITCBC 248   7 if (ex_ == other.ex_) 258   7 if (ex_ == other.ex_)
HITCBC 249   5 return true; 259   5 return true;
HITCBC 250   2 if (vt_ != other.vt_) 260   2 if (vt_ != other.vt_)
HITCBC 251   1 return false; 261   1 return false;
HITCBC 252   1 return vt_->equals(ex_, other.ex_); 262   1 return vt_->equals(ex_, other.ex_);
253   } 263   }
254   264  
255   /** Return a pointer to the wrapped executor if it matches 265   /** Return a pointer to the wrapped executor if it matches
256   the requested type. 266   the requested type.
257   267  
258   Performs a type check against the stored executor and 268   Performs a type check against the stored executor and
259   returns a typed pointer when the types match, or 269   returns a typed pointer when the types match, or
260   `nullptr` otherwise. Analogous to 270   `nullptr` otherwise. Analogous to
261   `std::any_cast< Executor >( &a )`. 271   `std::any_cast< Executor >( &a )`.
262   272  
263   @tparam Executor The executor type to retrieve. 273   @tparam Executor The executor type to retrieve.
264   274  
265   @return A pointer to the underlying executor, or 275   @return A pointer to the underlying executor, or
266   `nullptr` if the type does not match. 276   `nullptr` if the type does not match.
267   */ 277   */
268   template< typename Executor > 278   template< typename Executor >
HITCBC 269   2 const Executor* target() const 279   2 const Executor* target() const
270   { 280   {
HITCBC 271   2 if ( *vt_->type_id == detail::type_id< Executor >() ) 281   2 if ( *vt_->type_id == detail::type_id< Executor >() )
HITCBC 272   1 return static_cast< Executor const* >( ex_ ); 282   1 return static_cast< Executor const* >( ex_ );
HITCBC 273   1 return nullptr; 283   1 return nullptr;
274   } 284   }
275   285  
276   /// @copydoc target() const 286   /// @copydoc target() const
277   template< typename Executor> 287   template< typename Executor>
HITCBC 278   2 Executor* target() 288   2 Executor* target()
279   { 289   {
HITCBC 280   2 if ( *vt_->type_id == detail::type_id< Executor >() ) 290   2 if ( *vt_->type_id == detail::type_id< Executor >() )
281   return const_cast< Executor* >( 291   return const_cast< Executor* >(
HITCBC 282   1 static_cast< Executor const* >( ex_ )); 292   1 static_cast< Executor const* >( ex_ ));
HITCBC 283   1 return nullptr; 293   1 return nullptr;
284   } 294   }
285   }; 295   };
286   296  
287   } // capy 297   } // capy
288   } // boost 298   } // boost
289   299  
290   #endif 300   #endif