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