libs/capy/include/boost/capy/ex/executor_ref.hpp

78.8% Lines (26/33) 32.9% Functions (25/76) 75.0% Branches (3/4)
libs/capy/include/boost/capy/ex/executor_ref.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/coro.hpp>
16
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*, std::coroutine_handle<>);
36 void (*dispatch)(void const*, std::coroutine_handle<>);
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 [](void const* p) noexcept -> execution_context& {
46 return const_cast<Ex*>(static_cast<Ex const*>(p))->context();
47 },
48 // on_work_started
49 [](void const* p) noexcept {
50 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started();
51 },
52 // on_work_finished
53 [](void const* p) noexcept {
54 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_finished();
55 },
56 // post
57 94 [](void const* p, std::coroutine_handle<> h) {
58 47 static_cast<Ex const*>(p)->post(h);
59 },
60 // dispatch
61 596 [](void const* p, std::coroutine_handle<> h) {
62 298 static_cast<Ex const*>(p)->dispatch(h);
63 },
64 // equals
65 9 [](void const* a, void const* b) noexcept -> bool {
66 9 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_coroutine);
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 /** Default constructor.
118
119 Constructs an empty `executor_ref`. Calling any executor
120 operations on a default-constructed instance results in
121 undefined behavior.
122 */
123 6683 executor_ref() = default;
124
125 /** Copy constructor.
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 #endif
155 3674 executor_ref(Ex const& ex) noexcept
156 3674 : ex_(&ex)
157 3674 , vt_(&detail::vtable_for<Ex>)
158 {
159 3674 }
160
161 /** Returns true if this instance holds a valid executor.
162
163 @return `true` if constructed with an executor, `false` if
164 default-constructed.
165 */
166 22 explicit operator bool() const noexcept
167 {
168 22 return ex_ != nullptr;
169 }
170
171 /** Returns a reference to the associated execution context.
172
173 @return A reference to the execution context.
174
175 @pre This instance was constructed with a valid executor.
176 */
177 execution_context& context() const noexcept
178 {
179 return vt_->context(ex_);
180 }
181
182 /** Informs the executor that work is beginning.
183
184 Must be paired with a subsequent call to `on_work_finished()`.
185
186 @pre This instance was constructed with a valid executor.
187 */
188 void on_work_started() const noexcept
189 {
190 vt_->on_work_started(ex_);
191 }
192
193 /** Informs the executor that work has completed.
194
195 @pre A preceding call to `on_work_started()` was made.
196 @pre This instance was constructed with a valid executor.
197 */
198 void on_work_finished() const noexcept
199 {
200 vt_->on_work_finished(ex_);
201 }
202
203 /** Dispatches a coroutine handle through the wrapped executor.
204
205 Invokes the executor's `dispatch()` operation with the given
206 coroutine handle. If running in the executor's thread, resumes
207 the coroutine inline via a normal function call. Otherwise,
208 posts the coroutine for later execution.
209
210 @param h The coroutine handle to dispatch for resumption.
211
212 @pre This instance was constructed with a valid executor.
213 */
214 298 void dispatch(coro h) const
215 {
216 298 vt_->dispatch(ex_, h);
217 298 }
218
219 /** Posts a coroutine handle to the wrapped executor.
220
221 Posts the coroutine handle to the executor for later execution
222 and returns. The caller should transfer to `std::noop_coroutine()`
223 after calling this.
224
225 @param h The coroutine handle to post for resumption.
226
227 @pre This instance was constructed with a valid executor.
228 */
229 47 void post(coro h) const
230 {
231 47 vt_->post(ex_, h);
232 47 }
233
234 /** Compares two executor references for equality.
235
236 Two `executor_ref` instances are equal if they wrap
237 executors of the same type that compare equal.
238
239 @param other The executor reference to compare against.
240
241 @return `true` if both wrap equal executors of the same type.
242 */
243 3134 bool operator==(executor_ref const& other) const noexcept
244 {
245
2/2
✓ Branch 0 taken 3125 times.
✓ Branch 1 taken 9 times.
3134 if (ex_ == other.ex_)
246 3125 return true;
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (vt_ != other.vt_)
248 return false;
249 9 return vt_->equals(ex_, other.ex_);
250 }
251
252 /** Returns the type info of the underlying executor type.
253
254 @return A reference to the type_info for the wrapped executor.
255
256 @pre This instance was constructed with a valid executor.
257 */
258 4 detail::type_info const& type_id() const noexcept
259 {
260 4 return *vt_->type_id;
261 }
262 };
263
264 } // capy
265 } // boost
266
267 #endif
268