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