ex/executor_ref.hpp

78.9% Lines (30/38) 37.1% Functions (26/70) 75.0% Branches (6/8)
ex/executor_ref.hpp
Line Branch Hits 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 [](void const* p) noexcept -> execution_context& {
44 return const_cast<Ex*>(static_cast<Ex const*>(p))->context();
45 },
46 // on_work_started
47 [](void const* p) noexcept {
48 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started();
49 },
50 // on_work_finished
51 [](void const* p) noexcept {
52 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
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 if (ex_ == other.ex_)
245 5 return true;
246
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (vt_ != other.vt_)
247 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/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 if ( *vt_->type_id == detail::type_id< Executor >() )
268 1 return static_cast< Executor const* >( ex_ );
269 return nullptr;
270 }
271
272 /// @copydoc target() const
273 template< typename Executor>
274 2 Executor* target()
275 {
276
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
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
287