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
|