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

76.7% Lines (23/30) 30.9% Functions (21/68) 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/coro.hpp>
15
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*, std::coroutine_handle<>);
35 std::coroutine_handle<> (*dispatch)(void const*, std::coroutine_handle<>);
36 bool (*equals)(void const*, void const*) noexcept;
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 36 [](void const* p, std::coroutine_handle<> h) {
56 18 static_cast<Ex const*>(p)->post(h);
57 },
58 // dispatch
59 7 [](void const* p, std::coroutine_handle<> h) -> std::coroutine_handle<> {
60 7 return static_cast<Ex const*>(p)->dispatch(h);
61 },
62 // equals
63 9 [](void const* a, void const* b) noexcept -> bool {
64 9 return *static_cast<Ex const*>(a) == *static_cast<Ex const*>(b);
65 }
66 };
67
68 } // detail
69
70 /** A type-erased reference wrapper for executor objects.
71
72 This class provides type erasure for any executor type, enabling
73 runtime polymorphism without virtual functions or allocation.
74 It stores a pointer to the original executor and a pointer to a
75 static vtable, allowing executors of different types to be stored
76 uniformly while satisfying the full `Executor` concept.
77
78 @par Reference Semantics
79 This class has reference semantics: it does not allocate or own
80 the wrapped executor. Copy operations simply copy the internal
81 pointers. The caller must ensure the referenced executor outlives
82 all `executor_ref` instances that wrap it.
83
84 @par Thread Safety
85 The `executor_ref` itself is not thread-safe for concurrent
86 modification, but its executor operations are safe to call
87 concurrently if the underlying executor supports it.
88
89 @par Executor Concept
90 This class satisfies the `Executor` concept, making it usable
91 anywhere a concrete executor is expected.
92
93 @par Example
94 @code
95 void store_executor(executor_ref ex)
96 {
97 if(ex)
98 ex.post(my_coroutine);
99 }
100
101 io_context ctx;
102 store_executor(ctx.get_executor());
103 @endcode
104
105 @see any_executor, Executor
106 */
107 class executor_ref
108 {
109 void const* ex_ = nullptr;
110 detail::executor_vtable const* vt_ = nullptr;
111
112 public:
113 /** Default constructor.
114
115 Constructs an empty `executor_ref`. Calling any executor
116 operations on a default-constructed instance results in
117 undefined behavior.
118 */
119 5790 executor_ref() = default;
120
121 /** Copy constructor.
122
123 Copies the internal pointers, preserving identity.
124 This enables the same-executor optimization when passing
125 executor_ref through coroutine chains.
126 */
127 executor_ref(executor_ref const&) = default;
128
129 /** Copy assignment operator. */
130 executor_ref& operator=(executor_ref const&) = default;
131
132 /** Constructs from any executor type.
133
134 Captures a reference to the given executor and stores a pointer
135 to the type-specific vtable. The executor must remain valid for
136 the lifetime of this `executor_ref` instance.
137
138 @param ex The executor to wrap. Must satisfy the `Executor`
139 concept. A pointer to this object is stored
140 internally; the executor must outlive this wrapper.
141 */
142 #if defined(__GNUC__) && !defined(__clang__)
143 // GCC constraint satisfaction caching bug workaround
144 template<class Ex,
145 std::enable_if_t<!std::is_same_v<
146 std::decay_t<Ex>, executor_ref>, int> = 0>
147 #else
148 template<class Ex>
149 requires (!std::same_as<std::decay_t<Ex>, executor_ref>)
150 #endif
151 3545 executor_ref(Ex const& ex) noexcept
152 3545 : ex_(&ex)
153 3545 , vt_(&detail::vtable_for<Ex>)
154 {
155 3545 }
156
157 /** Returns true if this instance holds a valid executor.
158
159 @return `true` if constructed with an executor, `false` if
160 default-constructed.
161 */
162 22 explicit operator bool() const noexcept
163 {
164 22 return ex_ != nullptr;
165 }
166
167 /** Returns a reference to the associated execution context.
168
169 @return A reference to the execution context.
170
171 @pre This instance was constructed with a valid executor.
172 */
173 execution_context& context() const noexcept
174 {
175 return vt_->context(ex_);
176 }
177
178 /** Informs the executor that work is beginning.
179
180 Must be paired with a subsequent call to `on_work_finished()`.
181
182 @pre This instance was constructed with a valid executor.
183 */
184 void on_work_started() const noexcept
185 {
186 vt_->on_work_started(ex_);
187 }
188
189 /** Informs the executor that work has completed.
190
191 @pre A preceding call to `on_work_started()` was made.
192 @pre This instance was constructed with a valid executor.
193 */
194 void on_work_finished() const noexcept
195 {
196 vt_->on_work_finished(ex_);
197 }
198
199 /** Dispatches a coroutine handle through the wrapped executor.
200
201 Invokes the executor's `dispatch()` operation with the given
202 coroutine handle, returning a handle suitable for symmetric
203 transfer.
204
205 @param h The coroutine handle to dispatch for resumption.
206
207 @return A coroutine handle that the caller may use for symmetric
208 transfer, or `std::noop_coroutine()` if the executor
209 posted the work for later execution.
210
211 @pre This instance was constructed with a valid executor.
212 */
213 7 coro dispatch(coro h) const
214 {
215 7 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 18 void post(coro h) const
229 {
230 18 vt_->post(ex_, h);
231 18 }
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 2834 bool operator==(executor_ref const& other) const noexcept
243 {
244
2/2
✓ Branch 0 taken 2825 times.
✓ Branch 1 taken 9 times.
2834 if (ex_ == other.ex_)
245 2825 return true;
246
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (vt_ != other.vt_)
247 return false;
248 9 return vt_->equals(ex_, other.ex_);
249 }
250 };
251
252 } // capy
253 } // boost
254
255 #endif
256