ex/any_executor.hpp

75.5% Lines (40/53) 81.8% Functions (18/22) 82.4% Branches (14/17)
ex/any_executor.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_ANY_EXECUTOR_HPP
11 #define BOOST_CAPY_ANY_EXECUTOR_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <concepts>
15 #include <coroutine>
16 #include <memory>
17 #include <type_traits>
18 #include <typeinfo>
19
20 namespace boost {
21 namespace capy {
22
23 class execution_context;
24 template<typename> class strand;
25
26 namespace detail {
27
28 template<typename T>
29 struct is_strand_type : std::false_type {};
30
31 template<typename E>
32 struct is_strand_type<strand<E>> : std::true_type {};
33
34 } // detail
35
36 /** A type-erased wrapper for executor objects.
37
38 This class provides type erasure for any executor type, enabling
39 runtime polymorphism with automatic memory management via shared
40 ownership. It stores a shared pointer to a polymorphic wrapper,
41 allowing executors of different types to be stored uniformly
42 while satisfying the full `Executor` concept.
43
44 @par Value Semantics
45
46 This class has value semantics with shared ownership. Copy and
47 move operations are cheap, simply copying the internal shared
48 pointer. Multiple `any_executor` instances may share the same
49 underlying executor. Move operations do not invalidate the
50 source; there is no moved-from state.
51
52 @par Default State
53
54 A default-constructed `any_executor` holds no executor. Calling
55 executor operations on a default-constructed instance results
56 in undefined behavior. Use `operator bool()` to check validity.
57
58 @par Thread Safety
59
60 The `any_executor` itself is thread-safe for concurrent reads.
61 Concurrent modification requires external synchronization.
62 Executor operations are safe to call concurrently if the
63 underlying executor supports it.
64
65 @par Executor Concept
66
67 This class satisfies the `Executor` concept, making it usable
68 anywhere a concrete executor is expected.
69
70 @par Example
71 @code
72 any_executor exec = ctx.get_executor();
73 if(exec)
74 {
75 auto& context = exec.context();
76 exec.post(my_coroutine);
77 }
78 @endcode
79
80 @see executor_ref, Executor
81 */
82 class any_executor
83 {
84 struct impl_base;
85
86 std::shared_ptr<impl_base> p_;
87
88 struct impl_base
89 {
90 16 virtual ~impl_base() = default;
91 virtual execution_context& context() const noexcept = 0;
92 virtual void on_work_started() const noexcept = 0;
93 virtual void on_work_finished() const noexcept = 0;
94 virtual std::coroutine_handle<> dispatch(std::coroutine_handle<>) const = 0;
95 virtual void post(std::coroutine_handle<>) const = 0;
96 virtual bool equals(impl_base const*) const noexcept = 0;
97 virtual std::type_info const& target_type() const noexcept = 0;
98 };
99
100 template<class Ex>
101 struct impl final : impl_base
102 {
103 Ex ex_;
104
105 template<class Ex1>
106 16 explicit impl(Ex1&& ex)
107 16 : ex_(std::forward<Ex1>(ex))
108 {
109 16 }
110
111 5 execution_context& context() const noexcept override
112 {
113 5 return const_cast<Ex&>(ex_).context();
114 }
115
116 void on_work_started() const noexcept override
117 {
118 ex_.on_work_started();
119 }
120
121 void on_work_finished() const noexcept override
122 {
123 ex_.on_work_finished();
124 }
125
126 1 std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const override
127 {
128 1 return ex_.dispatch(h);
129 }
130
131 15 void post(std::coroutine_handle<> h) const override
132 {
133 15 ex_.post(h);
134 15 }
135
136 8 bool equals(impl_base const* other) const noexcept override
137 {
138
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
8 if(target_type() != other->target_type())
139 return false;
140 8 return ex_ == static_cast<impl const*>(other)->ex_;
141 }
142
143 17 std::type_info const& target_type() const noexcept override
144 {
145 17 return typeid(Ex);
146 }
147 };
148
149 public:
150 /** Default constructor.
151
152 Constructs an empty `any_executor`. Calling any executor
153 operations on a default-constructed instance results in
154 undefined behavior.
155
156 @par Postconditions
157 @li `!*this`
158 */
159 any_executor() = default;
160
161 /** Copy constructor.
162
163 Creates a new `any_executor` sharing ownership of the
164 underlying executor with `other`.
165
166 @par Postconditions
167 @li `*this == other`
168 */
169 9 any_executor(any_executor const&) = default;
170
171 /** Copy assignment operator.
172
173 Shares ownership of the underlying executor with `other`.
174
175 @par Postconditions
176 @li `*this == other`
177 */
178 2 any_executor& operator=(any_executor const&) = default;
179
180 /** Constructs from any executor type.
181
182 Allocates storage for a copy of the given executor and
183 stores it internally. The executor must satisfy the
184 `Executor` concept.
185
186 @param ex The executor to wrap. A copy is stored internally.
187
188 @par Postconditions
189 @li `*this` is valid
190 */
191 template<class Ex>
192 requires (
193 !std::same_as<std::decay_t<Ex>, any_executor> &&
194 !detail::is_strand_type<std::decay_t<Ex>>::value &&
195 std::copy_constructible<std::decay_t<Ex>>)
196 16 any_executor(Ex&& ex)
197
1/1
✓ Branch 2 taken 16 times.
16 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
198 {
199 16 }
200
201 /** Returns true if this instance holds a valid executor.
202
203 @return `true` if constructed with an executor, `false` if
204 default-constructed.
205 */
206 6 explicit operator bool() const noexcept
207 {
208 6 return p_ != nullptr;
209 }
210
211 /** Returns a reference to the associated execution context.
212
213 @return A reference to the execution context.
214
215 @pre This instance holds a valid executor.
216 */
217 5 execution_context& context() const noexcept
218 {
219 5 return p_->context();
220 }
221
222 /** Informs the executor that work is beginning.
223
224 Must be paired with a subsequent call to `on_work_finished()`.
225
226 @pre This instance holds a valid executor.
227 */
228 void on_work_started() const noexcept
229 {
230 p_->on_work_started();
231 }
232
233 /** Informs the executor that work has completed.
234
235 @pre A preceding call to `on_work_started()` was made.
236 @pre This instance holds a valid executor.
237 */
238 void on_work_finished() const noexcept
239 {
240 p_->on_work_finished();
241 }
242
243 /** Dispatches a coroutine handle through the wrapped executor.
244
245 Returns a handle for symmetric transfer. If running in the
246 executor's thread, returns `h`. Otherwise, posts the coroutine
247 for later execution and returns `std::noop_coroutine()`.
248
249 @param h The coroutine handle to dispatch for resumption.
250
251 @return A handle for symmetric transfer or `std::noop_coroutine()`.
252
253 @pre This instance holds a valid executor.
254 */
255 1 std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
256 {
257 1 return p_->dispatch(h);
258 }
259
260 /** Posts a coroutine handle to the wrapped executor.
261
262 Posts the coroutine handle to the executor for later execution
263 and returns. The caller should transfer to `std::noop_coroutine()`
264 after calling this.
265
266 @param h The coroutine handle to post for resumption.
267
268 @pre This instance holds a valid executor.
269 */
270 15 void post(std::coroutine_handle<> h) const
271 {
272 15 p_->post(h);
273 15 }
274
275 /** Compares two executor wrappers for equality.
276
277 Two `any_executor` instances are equal if they both hold
278 executors of the same type that compare equal, or if both
279 are empty.
280
281 @param other The executor to compare against.
282
283 @return `true` if both wrap equal executors of the same type,
284 or both are empty.
285 */
286 10 bool operator==(any_executor const& other) const noexcept
287 {
288
5/6
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 9 times.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 9 times.
10 if(!p_ && !other.p_)
289 1 return true;
290
5/6
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 8 times.
9 if(!p_ || !other.p_)
291 1 return false;
292 8 return p_->equals(other.p_.get());
293 }
294
295 /** Returns the type_info of the wrapped executor.
296
297 @return The `std::type_info` of the stored executor type,
298 or `typeid(void)` if empty.
299 */
300 2 std::type_info const& target_type() const noexcept
301 {
302
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if(!p_)
303 1 return typeid(void);
304 1 return p_->target_type();
305 }
306 };
307
308 } // capy
309 } // boost
310
311 #endif
312