include/boost/capy/ex/any_executor.hpp

100.0% Lines (64 / 64) 70.0% Functions (49 / 70)
any_executor.hpp
f(x) Functions (70)
Function Calls Lines Blocks
boost::capy::any_executor::impl_base::~impl_base() :88 34x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::impl<boost::capy::(anonymous namespace)::counting_executor&>(boost::capy::(anonymous namespace)::counting_executor&) :105 2x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::impl<boost::capy::(anonymous namespace)::counting_executor>(boost::capy::(anonymous namespace)::counting_executor&&) :105 4x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::impl<boost::capy::(anonymous namespace)::local_executor<0> >(boost::capy::(anonymous namespace)::local_executor<0>&&) :105 5x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::impl<boost::capy::(anonymous namespace)::local_executor<1> >(boost::capy::(anonymous namespace)::local_executor<1>&&) :105 1x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::impl<boost::capy::test::blocking_executor>(boost::capy::test::blocking_executor&&) :105 2x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::impl<boost::capy::thread_pool::executor_type&>(boost::capy::thread_pool::executor_type&) :105 11x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::impl<boost::capy::thread_pool::executor_type>(boost::capy::thread_pool::executor_type&&) :105 9x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::context() const :110 1x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::context() const :110 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::context() const :110 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::context() const :110 1x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::context() const :110 5x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::on_work_started() const :115 1x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::on_work_started() const :115 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::on_work_started() const :115 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::on_work_started() const :115 4x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::on_work_started() const :115 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::on_work_finished() const :120 1x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::on_work_finished() const :120 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::on_work_finished() const :120 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::on_work_finished() const :120 4x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::on_work_finished() const :120 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::dispatch(boost::capy::continuation&) const :125 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::dispatch(boost::capy::continuation&) const :125 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::dispatch(boost::capy::continuation&) const :125 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::dispatch(boost::capy::continuation&) const :125 4x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::dispatch(boost::capy::continuation&) const :125 1x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::post(boost::capy::continuation&) const :130 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::post(boost::capy::continuation&) const :130 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::post(boost::capy::continuation&) const :130 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::post(boost::capy::continuation&) const :130 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::post(boost::capy::continuation&) const :130 16x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::equals(boost::capy::any_executor::impl_base const*) const :135 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::equals(boost::capy::any_executor::impl_base const*) const :135 3x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::equals(boost::capy::any_executor::impl_base const*) const :135 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::equals(boost::capy::any_executor::impl_base const*) const :135 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::equals(boost::capy::any_executor::impl_base const*) const :135 9x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::target_type() const :142 8x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::target_type() const :142 14x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::target_type() const :142 1x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::target_type() const :142 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::target_type() const :142 27x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::counting_executor>::target_ptr() const :147 7x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<0> >::target_ptr() const :147 5x 100.0% 100.0% boost::capy::any_executor::impl<boost::capy::(anonymous namespace)::local_executor<1> >::target_ptr() const :147 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::test::blocking_executor>::target_ptr() const :147 0 0.0% 0.0% boost::capy::any_executor::impl<boost::capy::thread_pool::executor_type>::target_ptr() const :147 3x 100.0% 100.0% boost::capy::any_executor::any_executor() :164 2x 100.0% 100.0% boost::capy::any_executor::any_executor(boost::capy::any_executor const&) :176 35x 100.0% 100.0% boost::capy::any_executor::operator=(boost::capy::any_executor const&) :189 6x 100.0% 100.0% boost::capy::any_executor::any_executor<boost::capy::(anonymous namespace)::counting_executor&>(boost::capy::(anonymous namespace)::counting_executor&) :207 2x 100.0% 100.0% boost::capy::any_executor::any_executor<boost::capy::(anonymous namespace)::counting_executor>(boost::capy::(anonymous namespace)::counting_executor&&) :207 4x 100.0% 100.0% boost::capy::any_executor::any_executor<boost::capy::(anonymous namespace)::local_executor<0> >(boost::capy::(anonymous namespace)::local_executor<0>&&) :207 5x 100.0% 100.0% boost::capy::any_executor::any_executor<boost::capy::(anonymous namespace)::local_executor<1> >(boost::capy::(anonymous namespace)::local_executor<1>&&) :207 1x 100.0% 100.0% boost::capy::any_executor::any_executor<boost::capy::test::blocking_executor>(boost::capy::test::blocking_executor&&) :207 2x 100.0% 100.0% boost::capy::any_executor::any_executor<boost::capy::thread_pool::executor_type&>(boost::capy::thread_pool::executor_type&) :207 11x 100.0% 100.0% boost::capy::any_executor::any_executor<boost::capy::thread_pool::executor_type>(boost::capy::thread_pool::executor_type&&) :207 9x 100.0% 100.0% boost::capy::any_executor::operator bool() const :217 6x 100.0% 100.0% boost::capy::any_executor::context() const :228 7x 100.0% 100.0% boost::capy::any_executor::on_work_started() const :239 5x 100.0% 100.0% boost::capy::any_executor::on_work_finished() const :249 5x 100.0% 100.0% boost::capy::any_executor::dispatch(boost::capy::continuation&) const :268 5x 100.0% 100.0% boost::capy::any_executor::post(boost::capy::continuation&) const :284 16x 100.0% 100.0% boost::capy::any_executor::operator==(boost::capy::any_executor const&) const :300 14x 100.0% 100.0% boost::capy::any_executor::target_type() const :319 9x 100.0% 100.0% boost::capy::(anonymous namespace)::counting_executor const* boost::capy::any_executor::target<boost::capy::(anonymous namespace)::counting_executor>() const :344 2x 100.0% 100.0% boost::capy::(anonymous namespace)::local_executor<0> const* boost::capy::any_executor::target<boost::capy::(anonymous namespace)::local_executor<0> >() const :344 1x 75.0% 86.0% boost::capy::(anonymous namespace)::local_executor<1> const* boost::capy::any_executor::target<boost::capy::(anonymous namespace)::local_executor<1> >() const :344 1x 75.0% 79.0% int const* boost::capy::any_executor::target<int>() const :344 1x 75.0% 79.0%
Line TLA Hits 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 34x 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 34x explicit impl(Ex1&& ex)
106 34x : ex_(std::forward<Ex1>(ex))
107 {
108 34x }
109
110 7x execution_context& context() const noexcept override
111 {
112 7x return const_cast<Ex&>(ex_).context();
113 }
114
115 5x void on_work_started() const noexcept override
116 {
117 5x ex_.on_work_started();
118 5x }
119
120 5x void on_work_finished() const noexcept override
121 {
122 5x ex_.on_work_finished();
123 5x }
124
125 5x std::coroutine_handle<> dispatch(continuation& c) const override
126 {
127 5x return ex_.dispatch(c);
128 }
129
130 16x void post(continuation& c) const override
131 {
132 16x ex_.post(c);
133 16x }
134
135 12x bool equals(impl_base const* other) const noexcept override
136 {
137 12x if(target_type() != other->target_type())
138 2x return false;
139 10x return ex_ == static_cast<impl const*>(other)->ex_;
140 }
141
142 50x detail::type_info const& target_type() const noexcept override
143 {
144 50x return detail::type_id<Ex>();
145 }
146
147 15x void const* target_ptr() const noexcept override
148 {
149 15x 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 2x 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 35x 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 6x 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 34x any_executor(Ex&& ex)
208 34x : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
209 {
210 34x }
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 6x explicit operator bool() const noexcept
218 {
219 6x 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 7x execution_context& context() const noexcept
229 {
230 7x 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 5x void on_work_started() const noexcept
240 {
241 5x p_->on_work_started();
242 5x }
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 5x void on_work_finished() const noexcept
250 {
251 5x p_->on_work_finished();
252 5x }
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 5x std::coroutine_handle<> dispatch(continuation& c) const
269 {
270 5x 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 16x void post(continuation& c) const
285 {
286 16x p_->post(c);
287 16x }
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 14x bool operator==(any_executor const& other) const noexcept
301 {
302 14x if(!p_ && !other.p_)
303 1x return true;
304 13x if(!p_ || !other.p_)
305 1x return false;
306 12x 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 9x detail::type_info const& target_type() const noexcept
320 {
321 9x if(!p_)
322 3x return detail::type_id<void>();
323 6x 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 5x Ex const* target() const noexcept
345 {
346 5x if(!p_ || p_->target_type() != detail::type_id<Ex>())
347 3x return nullptr;
348 // Casting from void const* avoids naming impl<Ex>, which would
349 // instantiate its overrides and reject non-executor types
350 2x return static_cast<Ex const*>(p_->target_ptr());
351 }
352
353 /// @copydoc target() const
354 template<class Ex>
355 18x Ex* target() noexcept
356 {
357 18x if(!p_ || p_->target_type() != detail::type_id<Ex>())
358 5x return nullptr;
359 return const_cast<Ex*>(
360 13x static_cast<Ex const*>(p_->target_ptr()));
361 }
362 };
363
364 } // capy
365 } // boost
366
367 #endif
368