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