1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_ANY_EXECUTOR_HPP
10  
#ifndef BOOST_CAPY_ANY_EXECUTOR_HPP
11  
#define BOOST_CAPY_ANY_EXECUTOR_HPP
11  
#define BOOST_CAPY_ANY_EXECUTOR_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <concepts>
14  
#include <concepts>
15  
#include <coroutine>
15  
#include <coroutine>
16  
#include <memory>
16  
#include <memory>
17  
#include <type_traits>
17  
#include <type_traits>
18  
#include <typeinfo>
18  
#include <typeinfo>
19  

19  

20  
namespace boost {
20  
namespace boost {
21  
namespace capy {
21  
namespace capy {
22  

22  

23  
class execution_context;
23  
class execution_context;
24  
template<typename> class strand;
24  
template<typename> class strand;
25  

25  

26  
namespace detail {
26  
namespace detail {
27  

27  

28  
template<typename T>
28  
template<typename T>
29  
struct is_strand_type : std::false_type {};
29  
struct is_strand_type : std::false_type {};
30  

30  

31  
template<typename E>
31  
template<typename E>
32  
struct is_strand_type<strand<E>> : std::true_type {};
32  
struct is_strand_type<strand<E>> : std::true_type {};
33  

33  

34  
} // detail
34  
} // detail
35  

35  

36  
/** A type-erased wrapper for executor objects.
36  
/** A type-erased wrapper for executor objects.
37  

37  

38  
    This class provides type erasure for any executor type, enabling
38  
    This class provides type erasure for any executor type, enabling
39  
    runtime polymorphism with automatic memory management via shared
39  
    runtime polymorphism with automatic memory management via shared
40  
    ownership. It stores a shared pointer to a polymorphic wrapper,
40  
    ownership. It stores a shared pointer to a polymorphic wrapper,
41  
    allowing executors of different types to be stored uniformly
41  
    allowing executors of different types to be stored uniformly
42  
    while satisfying the full `Executor` concept.
42  
    while satisfying the full `Executor` concept.
43  

43  

44  
    @par Value Semantics
44  
    @par Value Semantics
45  

45  

46  
    This class has value semantics with shared ownership. Copy and
46  
    This class has value semantics with shared ownership. Copy and
47  
    move operations are cheap, simply copying the internal shared
47  
    move operations are cheap, simply copying the internal shared
48  
    pointer. Multiple `any_executor` instances may share the same
48  
    pointer. Multiple `any_executor` instances may share the same
49  
    underlying executor. Move operations do not invalidate the
49  
    underlying executor. Move operations do not invalidate the
50  
    source; there is no moved-from state.
50  
    source; there is no moved-from state.
51  

51  

52  
    @par Default State
52  
    @par Default State
53  

53  

54  
    A default-constructed `any_executor` holds no executor. Calling
54  
    A default-constructed `any_executor` holds no executor. Calling
55  
    executor operations on a default-constructed instance results
55  
    executor operations on a default-constructed instance results
56  
    in undefined behavior. Use `operator bool()` to check validity.
56  
    in undefined behavior. Use `operator bool()` to check validity.
57  

57  

58  
    @par Thread Safety
58  
    @par Thread Safety
59  

59  

60  
    The `any_executor` itself is thread-safe for concurrent reads.
60  
    The `any_executor` itself is thread-safe for concurrent reads.
61  
    Concurrent modification requires external synchronization.
61  
    Concurrent modification requires external synchronization.
62  
    Executor operations are safe to call concurrently if the
62  
    Executor operations are safe to call concurrently if the
63  
    underlying executor supports it.
63  
    underlying executor supports it.
64  

64  

65  
    @par Executor Concept
65  
    @par Executor Concept
66  

66  

67  
    This class satisfies the `Executor` concept, making it usable
67  
    This class satisfies the `Executor` concept, making it usable
68  
    anywhere a concrete executor is expected.
68  
    anywhere a concrete executor is expected.
69  

69  

70  
    @par Example
70  
    @par Example
71  
    @code
71  
    @code
72  
    any_executor exec = ctx.get_executor();
72  
    any_executor exec = ctx.get_executor();
73  
    if(exec)
73  
    if(exec)
74  
    {
74  
    {
75  
        auto& context = exec.context();
75  
        auto& context = exec.context();
76  
        exec.post(my_coroutine);
76  
        exec.post(my_coroutine);
77  
    }
77  
    }
78  
    @endcode
78  
    @endcode
79  

79  

80  
    @see executor_ref, Executor
80  
    @see executor_ref, Executor
81  
*/
81  
*/
82  
class any_executor
82  
class any_executor
83  
{
83  
{
84  
    struct impl_base;
84  
    struct impl_base;
85  

85  

86  
    std::shared_ptr<impl_base> p_;
86  
    std::shared_ptr<impl_base> p_;
87  

87  

88  
    struct impl_base
88  
    struct impl_base
89  
    {
89  
    {
90  
        virtual ~impl_base() = default;
90  
        virtual ~impl_base() = default;
91  
        virtual execution_context& context() const noexcept = 0;
91  
        virtual execution_context& context() const noexcept = 0;
92  
        virtual void on_work_started() const noexcept = 0;
92  
        virtual void on_work_started() const noexcept = 0;
93  
        virtual void on_work_finished() const noexcept = 0;
93  
        virtual void on_work_finished() const noexcept = 0;
94  
        virtual std::coroutine_handle<> dispatch(std::coroutine_handle<>) const = 0;
94  
        virtual std::coroutine_handle<> dispatch(std::coroutine_handle<>) const = 0;
95  
        virtual void post(std::coroutine_handle<>) const = 0;
95  
        virtual void post(std::coroutine_handle<>) const = 0;
96  
        virtual bool equals(impl_base const*) const noexcept = 0;
96  
        virtual bool equals(impl_base const*) const noexcept = 0;
97  
        virtual std::type_info const& target_type() const noexcept = 0;
97  
        virtual std::type_info const& target_type() const noexcept = 0;
98  
    };
98  
    };
99  

99  

100  
    template<class Ex>
100  
    template<class Ex>
101  
    struct impl final : impl_base
101  
    struct impl final : impl_base
102  
    {
102  
    {
103  
        Ex ex_;
103  
        Ex ex_;
104  

104  

105  
        template<class Ex1>
105  
        template<class Ex1>
106  
        explicit impl(Ex1&& ex)
106  
        explicit impl(Ex1&& ex)
107  
            : ex_(std::forward<Ex1>(ex))
107  
            : ex_(std::forward<Ex1>(ex))
108  
        {
108  
        {
109  
        }
109  
        }
110  

110  

111  
        execution_context& context() const noexcept override
111  
        execution_context& context() const noexcept override
112  
        {
112  
        {
113  
            return const_cast<Ex&>(ex_).context();
113  
            return const_cast<Ex&>(ex_).context();
114  
        }
114  
        }
115  

115  

116  
        void on_work_started() const noexcept override
116  
        void on_work_started() const noexcept override
117  
        {
117  
        {
118  
            ex_.on_work_started();
118  
            ex_.on_work_started();
119  
        }
119  
        }
120  

120  

121  
        void on_work_finished() const noexcept override
121  
        void on_work_finished() const noexcept override
122  
        {
122  
        {
123  
            ex_.on_work_finished();
123  
            ex_.on_work_finished();
124  
        }
124  
        }
125  

125  

126  
        std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const override
126  
        std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const override
127  
        {
127  
        {
128  
            return ex_.dispatch(h);
128  
            return ex_.dispatch(h);
129  
        }
129  
        }
130  

130  

131  
        void post(std::coroutine_handle<> h) const override
131  
        void post(std::coroutine_handle<> h) const override
132  
        {
132  
        {
133  
            ex_.post(h);
133  
            ex_.post(h);
134  
        }
134  
        }
135  

135  

136  
        bool equals(impl_base const* other) const noexcept override
136  
        bool equals(impl_base const* other) const noexcept override
137  
        {
137  
        {
138  
            if(target_type() != other->target_type())
138  
            if(target_type() != other->target_type())
139  
                return false;
139  
                return false;
140  
            return ex_ == static_cast<impl const*>(other)->ex_;
140  
            return ex_ == static_cast<impl const*>(other)->ex_;
141  
        }
141  
        }
142  

142  

143  
        std::type_info const& target_type() const noexcept override
143  
        std::type_info const& target_type() const noexcept override
144  
        {
144  
        {
145  
            return typeid(Ex);
145  
            return typeid(Ex);
146  
        }
146  
        }
147  
    };
147  
    };
148  

148  

149  
public:
149  
public:
150  
    /** Default constructor.
150  
    /** Default constructor.
151  

151  

152  
        Constructs an empty `any_executor`. Calling any executor
152  
        Constructs an empty `any_executor`. Calling any executor
153  
        operations on a default-constructed instance results in
153  
        operations on a default-constructed instance results in
154  
        undefined behavior.
154  
        undefined behavior.
155  

155  

156  
        @par Postconditions
156  
        @par Postconditions
157  
        @li `!*this`
157  
        @li `!*this`
158  
    */
158  
    */
159  
    any_executor() = default;
159  
    any_executor() = default;
160  

160  

161  
    /** Copy constructor.
161  
    /** Copy constructor.
162  

162  

163  
        Creates a new `any_executor` sharing ownership of the
163  
        Creates a new `any_executor` sharing ownership of the
164  
        underlying executor with `other`.
164  
        underlying executor with `other`.
165  

165  

166  
        @par Postconditions
166  
        @par Postconditions
167  
        @li `*this == other`
167  
        @li `*this == other`
168  
    */
168  
    */
169  
    any_executor(any_executor const&) = default;
169  
    any_executor(any_executor const&) = default;
170  

170  

171  
    /** Copy assignment operator.
171  
    /** Copy assignment operator.
172  

172  

173  
        Shares ownership of the underlying executor with `other`.
173  
        Shares ownership of the underlying executor with `other`.
174  

174  

175  
        @par Postconditions
175  
        @par Postconditions
176  
        @li `*this == other`
176  
        @li `*this == other`
177  
    */
177  
    */
178  
    any_executor& operator=(any_executor const&) = default;
178  
    any_executor& operator=(any_executor const&) = default;
179  

179  

180  
    /** Constructs from any executor type.
180  
    /** Constructs from any executor type.
181  

181  

182  
        Allocates storage for a copy of the given executor and
182  
        Allocates storage for a copy of the given executor and
183  
        stores it internally. The executor must satisfy the
183  
        stores it internally. The executor must satisfy the
184  
        `Executor` concept.
184  
        `Executor` concept.
185  

185  

186  
        @param ex The executor to wrap. A copy is stored internally.
186  
        @param ex The executor to wrap. A copy is stored internally.
187  

187  

188  
        @par Postconditions
188  
        @par Postconditions
189  
        @li `*this` is valid
189  
        @li `*this` is valid
190  
    */
190  
    */
191  
    template<class Ex>
191  
    template<class Ex>
192  
        requires (
192  
        requires (
193  
            !std::same_as<std::decay_t<Ex>, any_executor> &&
193  
            !std::same_as<std::decay_t<Ex>, any_executor> &&
194  
            !detail::is_strand_type<std::decay_t<Ex>>::value &&
194  
            !detail::is_strand_type<std::decay_t<Ex>>::value &&
195  
            std::copy_constructible<std::decay_t<Ex>>)
195  
            std::copy_constructible<std::decay_t<Ex>>)
196  
    any_executor(Ex&& ex)
196  
    any_executor(Ex&& ex)
197  
        : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
197  
        : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
198  
    {
198  
    {
199  
    }
199  
    }
200  

200  

201  
    /** Returns true if this instance holds a valid executor.
201  
    /** Returns true if this instance holds a valid executor.
202  

202  

203  
        @return `true` if constructed with an executor, `false` if
203  
        @return `true` if constructed with an executor, `false` if
204  
                default-constructed.
204  
                default-constructed.
205  
    */
205  
    */
206  
    explicit operator bool() const noexcept
206  
    explicit operator bool() const noexcept
207  
    {
207  
    {
208  
        return p_ != nullptr;
208  
        return p_ != nullptr;
209  
    }
209  
    }
210  

210  

211  
    /** Returns a reference to the associated execution context.
211  
    /** Returns a reference to the associated execution context.
212  

212  

213  
        @return A reference to the execution context.
213  
        @return A reference to the execution context.
214  

214  

215  
        @pre This instance holds a valid executor.
215  
        @pre This instance holds a valid executor.
216  
    */
216  
    */
217  
    execution_context& context() const noexcept
217  
    execution_context& context() const noexcept
218  
    {
218  
    {
219  
        return p_->context();
219  
        return p_->context();
220  
    }
220  
    }
221  

221  

222  
    /** Informs the executor that work is beginning.
222  
    /** Informs the executor that work is beginning.
223  

223  

224  
        Must be paired with a subsequent call to `on_work_finished()`.
224  
        Must be paired with a subsequent call to `on_work_finished()`.
225  

225  

226  
        @pre This instance holds a valid executor.
226  
        @pre This instance holds a valid executor.
227  
    */
227  
    */
228  
    void on_work_started() const noexcept
228  
    void on_work_started() const noexcept
229  
    {
229  
    {
230  
        p_->on_work_started();
230  
        p_->on_work_started();
231  
    }
231  
    }
232  

232  

233  
    /** Informs the executor that work has completed.
233  
    /** Informs the executor that work has completed.
234  

234  

235  
        @pre A preceding call to `on_work_started()` was made.
235  
        @pre A preceding call to `on_work_started()` was made.
236  
        @pre This instance holds a valid executor.
236  
        @pre This instance holds a valid executor.
237  
    */
237  
    */
238  
    void on_work_finished() const noexcept
238  
    void on_work_finished() const noexcept
239  
    {
239  
    {
240  
        p_->on_work_finished();
240  
        p_->on_work_finished();
241  
    }
241  
    }
242  

242  

243  
    /** Dispatches a coroutine handle through the wrapped executor.
243  
    /** Dispatches a coroutine handle through the wrapped executor.
244  

244  

245  
        Returns a handle for symmetric transfer. If running in the
245  
        Returns a handle for symmetric transfer. If running in the
246  
        executor's thread, returns `h`. Otherwise, posts the coroutine
246  
        executor's thread, returns `h`. Otherwise, posts the coroutine
247  
        for later execution and returns `std::noop_coroutine()`.
247  
        for later execution and returns `std::noop_coroutine()`.
248  

248  

249  
        @param h The coroutine handle to dispatch for resumption.
249  
        @param h The coroutine handle to dispatch for resumption.
250  

250  

251  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
251  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
252  

252  

253  
        @pre This instance holds a valid executor.
253  
        @pre This instance holds a valid executor.
254  
    */
254  
    */
255  
    std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
255  
    std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
256  
    {
256  
    {
257  
        return p_->dispatch(h);
257  
        return p_->dispatch(h);
258  
    }
258  
    }
259  

259  

260  
    /** Posts a coroutine handle to the wrapped executor.
260  
    /** Posts a coroutine handle to the wrapped executor.
261  

261  

262  
        Posts the coroutine handle to the executor for later execution
262  
        Posts the coroutine handle to the executor for later execution
263  
        and returns. The caller should transfer to `std::noop_coroutine()`
263  
        and returns. The caller should transfer to `std::noop_coroutine()`
264  
        after calling this.
264  
        after calling this.
265  

265  

266  
        @param h The coroutine handle to post for resumption.
266  
        @param h The coroutine handle to post for resumption.
267  

267  

268  
        @pre This instance holds a valid executor.
268  
        @pre This instance holds a valid executor.
269  
    */
269  
    */
270  
    void post(std::coroutine_handle<> h) const
270  
    void post(std::coroutine_handle<> h) const
271  
    {
271  
    {
272  
        p_->post(h);
272  
        p_->post(h);
273  
    }
273  
    }
274  

274  

275  
    /** Compares two executor wrappers for equality.
275  
    /** Compares two executor wrappers for equality.
276  

276  

277  
        Two `any_executor` instances are equal if they both hold
277  
        Two `any_executor` instances are equal if they both hold
278  
        executors of the same type that compare equal, or if both
278  
        executors of the same type that compare equal, or if both
279  
        are empty.
279  
        are empty.
280  

280  

281  
        @param other The executor to compare against.
281  
        @param other The executor to compare against.
282  

282  

283  
        @return `true` if both wrap equal executors of the same type,
283  
        @return `true` if both wrap equal executors of the same type,
284  
                or both are empty.
284  
                or both are empty.
285  
    */
285  
    */
286  
    bool operator==(any_executor const& other) const noexcept
286  
    bool operator==(any_executor const& other) const noexcept
287  
    {
287  
    {
288  
        if(!p_ && !other.p_)
288  
        if(!p_ && !other.p_)
289  
            return true;
289  
            return true;
290  
        if(!p_ || !other.p_)
290  
        if(!p_ || !other.p_)
291  
            return false;
291  
            return false;
292  
        return p_->equals(other.p_.get());
292  
        return p_->equals(other.p_.get());
293  
    }
293  
    }
294  

294  

295  
    /** Returns the type_info of the wrapped executor.
295  
    /** Returns the type_info of the wrapped executor.
296  

296  

297  
        @return The `std::type_info` of the stored executor type,
297  
        @return The `std::type_info` of the stored executor type,
298  
                or `typeid(void)` if empty.
298  
                or `typeid(void)` if empty.
299  
    */
299  
    */
300  
    std::type_info const& target_type() const noexcept
300  
    std::type_info const& target_type() const noexcept
301  
    {
301  
    {
302  
        if(!p_)
302  
        if(!p_)
303  
            return typeid(void);
303  
            return typeid(void);
304  
        return p_->target_type();
304  
        return p_->target_type();
305  
    }
305  
    }
306  
};
306  
};
307  

307  

308  
} // capy
308  
} // capy
309  
} // boost
309  
} // boost
310  

310  

311  
#endif
311  
#endif