GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/any_executor.hpp
Date: 2026-01-19 05:31:50
Exec Total Coverage
Lines: 40 47 85.1%
Functions: 18 20 90.0%
Branches: 14 17 82.4%

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