Line data 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 0 : [](void const* p) noexcept -> execution_context& {
44 0 : return const_cast<Ex*>(static_cast<Ex const*>(p))->context();
45 : },
46 : // on_work_started
47 0 : [](void const* p) noexcept {
48 0 : const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started();
49 : },
50 : // on_work_finished
51 0 : [](void const* p) noexcept {
52 0 : 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 : class executor_ref
94 : {
95 : void const* ex_ = nullptr;
96 : detail::executor_vtable const* vt_ = nullptr;
97 :
98 : public:
99 : /** Default constructor.
100 :
101 : Constructs an empty `executor_ref`. Calling any executor
102 : operations on a default-constructed instance results in
103 : undefined behavior.
104 : */
105 5790 : executor_ref() = default;
106 :
107 : /** Copy constructor.
108 :
109 : Copies the internal pointers, preserving identity.
110 : This enables the same-executor optimization when passing
111 : executor_ref through coroutine chains.
112 : */
113 : executor_ref(executor_ref const&) = default;
114 :
115 : /** Copy assignment operator. */
116 : executor_ref& operator=(executor_ref const&) = default;
117 :
118 : /** Constructs from any executor type.
119 :
120 : Captures a reference to the given executor and stores a pointer
121 : to the type-specific vtable. The executor must remain valid for
122 : the lifetime of this `executor_ref` instance.
123 :
124 : @param ex The executor to wrap. Must satisfy the `Executor`
125 : concept. A pointer to this object is stored
126 : internally; the executor must outlive this wrapper.
127 : */
128 : #if defined(__GNUC__) && !defined(__clang__)
129 : // GCC constraint satisfaction caching bug workaround
130 : template<class Ex,
131 : std::enable_if_t<!std::is_same_v<
132 : std::decay_t<Ex>, executor_ref>, int> = 0>
133 : #else
134 : template<class Ex>
135 : requires (!std::same_as<std::decay_t<Ex>, executor_ref>)
136 : #endif
137 3545 : executor_ref(Ex const& ex) noexcept
138 3545 : : ex_(&ex)
139 3545 : , vt_(&detail::vtable_for<Ex>)
140 : {
141 3545 : }
142 :
143 : /** Returns true if this instance holds a valid executor.
144 :
145 : @return `true` if constructed with an executor, `false` if
146 : default-constructed.
147 : */
148 22 : explicit operator bool() const noexcept
149 : {
150 22 : return ex_ != nullptr;
151 : }
152 :
153 : /** Returns a reference to the associated execution context.
154 :
155 : @return A reference to the execution context.
156 :
157 : @pre This instance was constructed with a valid executor.
158 : */
159 : execution_context& context() const noexcept
160 : {
161 : return vt_->context(ex_);
162 : }
163 :
164 : /** Informs the executor that work is beginning.
165 :
166 : Must be paired with a subsequent call to `on_work_finished()`.
167 :
168 : @pre This instance was constructed with a valid executor.
169 : */
170 : void on_work_started() const noexcept
171 : {
172 : vt_->on_work_started(ex_);
173 : }
174 :
175 : /** Informs the executor that work has completed.
176 :
177 : @pre A preceding call to `on_work_started()` was made.
178 : @pre This instance was constructed with a valid executor.
179 : */
180 : void on_work_finished() const noexcept
181 : {
182 : vt_->on_work_finished(ex_);
183 : }
184 :
185 : /** Dispatches a coroutine handle through the wrapped executor.
186 :
187 : Invokes the executor's `dispatch()` operation with the given
188 : coroutine handle, returning a handle suitable for symmetric
189 : transfer.
190 :
191 : @param h The coroutine handle to dispatch for resumption.
192 :
193 : @return A coroutine handle that the caller may use for symmetric
194 : transfer, or `std::noop_coroutine()` if the executor
195 : posted the work for later execution.
196 :
197 : @pre This instance was constructed with a valid executor.
198 : */
199 7 : coro dispatch(coro h) const
200 : {
201 7 : return vt_->dispatch(ex_, h);
202 : }
203 :
204 : /** Posts a coroutine handle to the wrapped executor.
205 :
206 : Posts the coroutine handle to the executor for later execution
207 : and returns. The caller should transfer to `std::noop_coroutine()`
208 : after calling this.
209 :
210 : @param h The coroutine handle to post for resumption.
211 :
212 : @pre This instance was constructed with a valid executor.
213 : */
214 18 : void post(coro h) const
215 : {
216 18 : vt_->post(ex_, h);
217 18 : }
218 :
219 : /** Compares two executor references for equality.
220 :
221 : Two `executor_ref` instances are equal if they wrap
222 : executors of the same type that compare equal.
223 :
224 : @param other The executor reference to compare against.
225 :
226 : @return `true` if both wrap equal executors of the same type.
227 : */
228 2834 : bool operator==(executor_ref const& other) const noexcept
229 : {
230 2834 : if (ex_ == other.ex_)
231 2825 : return true;
232 9 : if (vt_ != other.vt_)
233 0 : return false;
234 9 : return vt_->equals(ex_, other.ex_);
235 : }
236 : };
237 :
238 : } // capy
239 : } // boost
240 :
241 : #endif
|