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