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

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