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 @see executor_ref, Executor
73 */
74 class any_executor
75 {
76 struct impl_base;
77
78 std::shared_ptr<impl_base> p_;
79
80 struct impl_base
81 {
82 16 virtual ~impl_base() = default;
83 virtual execution_context& context() const noexcept = 0;
84 virtual void on_work_started() const noexcept = 0;
85 virtual void on_work_finished() const noexcept = 0;
86 virtual std::coroutine_handle<> dispatch(std::coroutine_handle<>) const = 0;
87 virtual void post(std::coroutine_handle<>) const = 0;
88 virtual bool equals(impl_base const*) const noexcept = 0;
89 virtual std::type_info const& target_type() const noexcept = 0;
90 };
91
92 template<class Ex>
93 struct impl final : impl_base
94 {
95 Ex ex_;
96
97 template<class Ex1>
98 16 explicit impl(Ex1&& ex)
99 16 : ex_(std::forward<Ex1>(ex))
100 {
101 16 }
102
103 5 execution_context& context() const noexcept override
104 {
105 5 return const_cast<Ex&>(ex_).context();
106 }
107
108 void on_work_started() const noexcept override
109 {
110 ex_.on_work_started();
111 }
112
113 void on_work_finished() const noexcept override
114 {
115 ex_.on_work_finished();
116 }
117
118 1 std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const override
119 {
120 1 return ex_.dispatch(h);
121 }
122
123 15 void post(std::coroutine_handle<> h) const override
124 {
125 15 ex_.post(h);
126 15 }
127
128 8 bool equals(impl_base const* other) const noexcept override
129 {
130
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
8 if(target_type() != other->target_type())
131 return false;
132 8 return ex_ == static_cast<impl const*>(other)->ex_;
133 }
134
135 17 std::type_info const& target_type() const noexcept override
136 {
137 17 return typeid(Ex);
138 }
139 };
140
141 public:
142 /** Default constructor.
143
144 Constructs an empty `any_executor`. Calling any executor
145 operations on a default-constructed instance results in
146 undefined behavior.
147
148 @par Postconditions
149 @li `!*this`
150 */
151 any_executor() = default;
152
153 /** Copy constructor.
154
155 Creates a new `any_executor` sharing ownership of the
156 underlying executor with `other`.
157
158 @par Postconditions
159 @li `*this == other`
160 */
161 9 any_executor(any_executor const&) = default;
162
163 /** Copy assignment operator.
164
165 Shares ownership of the underlying executor with `other`.
166
167 @par Postconditions
168 @li `*this == other`
169 */
170 2 any_executor& operator=(any_executor const&) = default;
171
172 /** Constructs from any executor type.
173
174 Allocates storage for a copy of the given executor and
175 stores it internally. The executor must satisfy the
176 `Executor` concept.
177
178 @param ex The executor to wrap. A copy is stored internally.
179
180 @par Postconditions
181 @li `*this` is valid
182 */
183 template<class Ex>
184 requires (
185 !std::same_as<std::decay_t<Ex>, any_executor> &&
186 !detail::is_strand_type<std::decay_t<Ex>>::value &&
187 std::copy_constructible<std::decay_t<Ex>>)
188 16 any_executor(Ex&& ex)
189
1/1
✓ Branch 2 taken 16 times.
16 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
190 {
191 16 }
192
193 /** Returns true if this instance holds a valid executor.
194
195 @return `true` if constructed with an executor, `false` if
196 default-constructed.
197 */
198 6 explicit operator bool() const noexcept
199 {
200 6 return p_ != nullptr;
201 }
202
203 /** Returns a reference to the associated execution context.
204
205 @return A reference to the execution context.
206
207 @pre This instance holds a valid executor.
208 */
209 5 execution_context& context() const noexcept
210 {
211 5 return p_->context();
212 }
213
214 /** Informs the executor that work is beginning.
215
216 Must be paired with a subsequent call to `on_work_finished()`.
217
218 @pre This instance holds a valid executor.
219 */
220 void on_work_started() const noexcept
221 {
222 p_->on_work_started();
223 }
224
225 /** Informs the executor that work has completed.
226
227 @pre A preceding call to `on_work_started()` was made.
228 @pre This instance holds a valid executor.
229 */
230 void on_work_finished() const noexcept
231 {
232 p_->on_work_finished();
233 }
234
235 /** Dispatches a coroutine handle through the wrapped executor.
236
237 Invokes the executor's `dispatch()` operation with the given
238 coroutine handle, returning a handle suitable for symmetric
239 transfer.
240
241 @param h The coroutine handle to dispatch for resumption.
242
243 @return A coroutine handle that the caller may use for symmetric
244 transfer, or `std::noop_coroutine()` if the executor
245 posted the work for later execution.
246
247 @pre This instance holds a valid executor.
248 */
249 1 coro dispatch(coro h) const
250 {
251 1 return p_->dispatch(h);
252 }
253
254 /** Posts a coroutine handle to the wrapped executor.
255
256 Posts the coroutine handle to the executor for later execution
257 and returns. The caller should transfer to `std::noop_coroutine()`
258 after calling this.
259
260 @param h The coroutine handle to post for resumption.
261
262 @pre This instance holds a valid executor.
263 */
264 15 void post(coro h) const
265 {
266 15 p_->post(h);
267 15 }
268
269 /** Compares two executor wrappers for equality.
270
271 Two `any_executor` instances are equal if they both hold
272 executors of the same type that compare equal, or if both
273 are empty.
274
275 @param other The executor to compare against.
276
277 @return `true` if both wrap equal executors of the same type,
278 or both are empty.
279 */
280 10 bool operator==(any_executor const& other) const noexcept
281 {
282
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_)
283 1 return true;
284
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_)
285 1 return false;
286 8 return p_->equals(other.p_.get());
287 }
288
289 /** Returns the type_info of the wrapped executor.
290
291 @return The `std::type_info` of the stored executor type,
292 or `typeid(void)` if empty.
293 */
294 2 std::type_info const& target_type() const noexcept
295 {
296
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if(!p_)
297 1 return typeid(void);
298 1 return p_->target_type();
299 }
300 };
301
302 } // capy
303 } // boost
304
305 #endif
306