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_RUN_HPP
10  
#ifndef BOOST_CAPY_RUN_HPP
11  
#define BOOST_CAPY_RUN_HPP
11  
#define BOOST_CAPY_RUN_HPP
12  

12  

13 -
#include <boost/capy/detail/await_suspend_helper.hpp>
 
14  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
15  
#include <boost/capy/detail/run.hpp>
14  
#include <boost/capy/detail/run.hpp>
16  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/io_runnable.hpp>
16  
#include <boost/capy/concept/io_runnable.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <coroutine>
18  
#include <coroutine>
20  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
21  
#include <boost/capy/ex/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
22  

21  

23  
#include <memory_resource>
22  
#include <memory_resource>
24  
#include <stop_token>
23  
#include <stop_token>
25  
#include <type_traits>
24  
#include <type_traits>
26  
#include <utility>
25  
#include <utility>
27  
#include <variant>
26  
#include <variant>
28  

27  

29  
/*
28  
/*
30  
    Allocator Lifetime Strategy
29  
    Allocator Lifetime Strategy
31  
    ===========================
30  
    ===========================
32  

31  

33  
    When using run() with a custom allocator:
32  
    When using run() with a custom allocator:
34  

33  

35  
        co_await run(ex, alloc)(my_task());
34  
        co_await run(ex, alloc)(my_task());
36  

35  

37  
    The evaluation order is:
36  
    The evaluation order is:
38  
        1. run(ex, alloc) creates a temporary wrapper
37  
        1. run(ex, alloc) creates a temporary wrapper
39  
        2. my_task() allocates its coroutine frame using TLS
38  
        2. my_task() allocates its coroutine frame using TLS
40  
        3. operator() returns an awaitable
39  
        3. operator() returns an awaitable
41  
        4. Wrapper temporary is DESTROYED
40  
        4. Wrapper temporary is DESTROYED
42  
        5. co_await suspends caller, resumes task
41  
        5. co_await suspends caller, resumes task
43  
        6. Task body executes (wrapper is already dead!)
42  
        6. Task body executes (wrapper is already dead!)
44  

43  

45  
    Problem: The wrapper's frame_memory_resource dies before the task
44  
    Problem: The wrapper's frame_memory_resource dies before the task
46  
    body runs. When initial_suspend::await_resume() restores TLS from
45  
    body runs. When initial_suspend::await_resume() restores TLS from
47  
    the saved pointer, it would point to dead memory.
46  
    the saved pointer, it would point to dead memory.
48  

47  

49  
    Solution: Store a COPY of the allocator in the awaitable (not just
48  
    Solution: Store a COPY of the allocator in the awaitable (not just
50  
    the wrapper). The co_await mechanism extends the awaitable's lifetime
49  
    the wrapper). The co_await mechanism extends the awaitable's lifetime
51  
    until the await completes. In await_suspend, we overwrite the promise's
50  
    until the await completes. In await_suspend, we overwrite the promise's
52  
    saved frame_allocator pointer to point to the awaitable's resource.
51  
    saved frame_allocator pointer to point to the awaitable's resource.
53  

52  

54  
    This works because standard allocator copies are equivalent - memory
53  
    This works because standard allocator copies are equivalent - memory
55  
    allocated with one copy can be deallocated with another copy. The
54  
    allocated with one copy can be deallocated with another copy. The
56  
    task's own frame uses the footer-stored pointer (safe), while nested
55  
    task's own frame uses the footer-stored pointer (safe), while nested
57  
    task creation uses TLS pointing to the awaitable's resource (also safe).
56  
    task creation uses TLS pointing to the awaitable's resource (also safe).
58  
*/
57  
*/
59  

58  

60  
namespace boost::capy::detail {
59  
namespace boost::capy::detail {
61 -
//----------------------------------------------------------
 
62 -
//
 
63 -
// dispatch_trampoline - cross-executor dispatch
 
64 -
//
 
65 -
//----------------------------------------------------------
 
66 -

 
67  

60  

68  
/** Minimal coroutine that dispatches through the caller's executor.
61  
/** Minimal coroutine that dispatches through the caller's executor.
69  

62  

70  
    Sits between the inner task and the parent when executors
63  
    Sits between the inner task and the parent when executors
71  
    diverge. The inner task's `final_suspend` resumes this
64  
    diverge. The inner task's `final_suspend` resumes this
72  
    trampoline via symmetric transfer. The trampoline's own
65  
    trampoline via symmetric transfer. The trampoline's own
73  
    `final_suspend` dispatches the parent through the caller's
66  
    `final_suspend` dispatches the parent through the caller's
74  
    executor to restore the correct execution context.
67  
    executor to restore the correct execution context.
75  

68  

76  
    The trampoline never touches the task's result.
69  
    The trampoline never touches the task's result.
77  
*/
70  
*/
78  
struct dispatch_trampoline
71  
struct dispatch_trampoline
79  
{
72  
{
80  
    struct promise_type
73  
    struct promise_type
81  
    {
74  
    {
82  
        executor_ref caller_ex_;
75  
        executor_ref caller_ex_;
83  
        std::coroutine_handle<> parent_;
76  
        std::coroutine_handle<> parent_;
84  

77  

85  
        dispatch_trampoline get_return_object() noexcept
78  
        dispatch_trampoline get_return_object() noexcept
86  
        {
79  
        {
87  
            return dispatch_trampoline{
80  
            return dispatch_trampoline{
88  
                std::coroutine_handle<promise_type>::from_promise(*this)};
81  
                std::coroutine_handle<promise_type>::from_promise(*this)};
89  
        }
82  
        }
90  

83  

91  
        std::suspend_always initial_suspend() noexcept { return {}; }
84  
        std::suspend_always initial_suspend() noexcept { return {}; }
92  

85  

93  
        auto final_suspend() noexcept
86  
        auto final_suspend() noexcept
94  
        {
87  
        {
95  
            struct awaiter
88  
            struct awaiter
96  
            {
89  
            {
97  
                promise_type* p_;
90  
                promise_type* p_;
98  
                bool await_ready() const noexcept { return false; }
91  
                bool await_ready() const noexcept { return false; }
99  

92  

100 -
                auto await_suspend(
93 +
                std::coroutine_handle<> await_suspend(
101  
                    std::coroutine_handle<>) noexcept
94  
                    std::coroutine_handle<>) noexcept
102  
                {
95  
                {
103 -
                    return detail::symmetric_transfer(
96 +
                    return p_->caller_ex_.dispatch(p_->parent_);
104 -
                        p_->caller_ex_.dispatch(p_->parent_));
 
105  
                }
97  
                }
106  

98  

107  
                void await_resume() const noexcept {}
99  
                void await_resume() const noexcept {}
108  
            };
100  
            };
109  
            return awaiter{this};
101  
            return awaiter{this};
110  
        }
102  
        }
111  

103  

112  
        void return_void() noexcept {}
104  
        void return_void() noexcept {}
113  
        void unhandled_exception() noexcept {}
105  
        void unhandled_exception() noexcept {}
114  
    };
106  
    };
115  

107  

116  
    std::coroutine_handle<promise_type> h_{nullptr};
108  
    std::coroutine_handle<promise_type> h_{nullptr};
117  

109  

118  
    dispatch_trampoline() noexcept = default;
110  
    dispatch_trampoline() noexcept = default;
119  

111  

120  
    ~dispatch_trampoline()
112  
    ~dispatch_trampoline()
121  
    {
113  
    {
122  
        if(h_) h_.destroy();
114  
        if(h_) h_.destroy();
123  
    }
115  
    }
124  

116  

125  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
117  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
126  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
118  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
127  

119  

128  
    dispatch_trampoline(dispatch_trampoline&& o) noexcept
120  
    dispatch_trampoline(dispatch_trampoline&& o) noexcept
129  
        : h_(std::exchange(o.h_, nullptr)) {}
121  
        : h_(std::exchange(o.h_, nullptr)) {}
130  

122  

131  
    dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
123  
    dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
132  
    {
124  
    {
133  
        if(this != &o)
125  
        if(this != &o)
134  
        {
126  
        {
135  
            if(h_) h_.destroy();
127  
            if(h_) h_.destroy();
136  
            h_ = std::exchange(o.h_, nullptr);
128  
            h_ = std::exchange(o.h_, nullptr);
137  
        }
129  
        }
138  
        return *this;
130  
        return *this;
139  
    }
131  
    }
140  

132  

141  
private:
133  
private:
142  
    explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
134  
    explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
143  
        : h_(h) {}
135  
        : h_(h) {}
144  
};
136  
};
145  

137  

146  
inline dispatch_trampoline make_dispatch_trampoline()
138  
inline dispatch_trampoline make_dispatch_trampoline()
147  
{
139  
{
148  
    co_return;
140  
    co_return;
149  
}
141  
}
150 -
//----------------------------------------------------------
 
151 -
//
 
152 -
// run_awaitable_ex - with executor (executor switch)
 
153 -
//
 
154 -
//----------------------------------------------------------
 
155 -

 
156  

142  

157  
/** Awaitable that binds an IoRunnable to a specific executor.
143  
/** Awaitable that binds an IoRunnable to a specific executor.
158  

144  

159  
    Stores the executor and inner task by value. When co_awaited, the
145  
    Stores the executor and inner task by value. When co_awaited, the
160  
    co_await expression's lifetime extension keeps both alive for the
146  
    co_await expression's lifetime extension keeps both alive for the
161  
    duration of the operation.
147  
    duration of the operation.
162  

148  

163  
    A dispatch trampoline handles the executor switch on completion:
149  
    A dispatch trampoline handles the executor switch on completion:
164  
    the inner task's `final_suspend` resumes the trampoline, which
150  
    the inner task's `final_suspend` resumes the trampoline, which
165  
    dispatches back through the caller's executor.
151  
    dispatches back through the caller's executor.
166  

152  

167  
    The `io_env` is owned by this awaitable and is guaranteed to
153  
    The `io_env` is owned by this awaitable and is guaranteed to
168  
    outlive the inner task and all awaitables in its chain. Awaitables
154  
    outlive the inner task and all awaitables in its chain. Awaitables
169  
    may store `io_env const*` without concern for dangling references.
155  
    may store `io_env const*` without concern for dangling references.
170  

156  

171  
    @tparam Task The IoRunnable type
157  
    @tparam Task The IoRunnable type
172  
    @tparam Ex The executor type
158  
    @tparam Ex The executor type
173  
    @tparam InheritStopToken If true, inherit caller's stop token
159  
    @tparam InheritStopToken If true, inherit caller's stop token
174  
    @tparam Alloc The allocator type (void for no allocator)
160  
    @tparam Alloc The allocator type (void for no allocator)
175  
*/
161  
*/
176  
template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
162  
template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
177  
struct [[nodiscard]] run_awaitable_ex
163  
struct [[nodiscard]] run_awaitable_ex
178  
{
164  
{
179  
    Ex ex_;
165  
    Ex ex_;
180  
    frame_memory_resource<Alloc> resource_;
166  
    frame_memory_resource<Alloc> resource_;
181  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
167  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
182  
    io_env env_;
168  
    io_env env_;
183  
    dispatch_trampoline tr_;
169  
    dispatch_trampoline tr_;
184  
    Task inner_;  // Last: destroyed first, while env_ is still valid
170  
    Task inner_;  // Last: destroyed first, while env_ is still valid
185  

171  

186  
    // void allocator, inherit stop token
172  
    // void allocator, inherit stop token
187  
    run_awaitable_ex(Ex ex, Task inner)
173  
    run_awaitable_ex(Ex ex, Task inner)
188  
        requires (InheritStopToken && std::is_void_v<Alloc>)
174  
        requires (InheritStopToken && std::is_void_v<Alloc>)
189  
        : ex_(std::move(ex))
175  
        : ex_(std::move(ex))
190  
        , inner_(std::move(inner))
176  
        , inner_(std::move(inner))
191  
    {
177  
    {
192  
    }
178  
    }
193  

179  

194  
    // void allocator, explicit stop token
180  
    // void allocator, explicit stop token
195  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
181  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
196  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
182  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
197  
        : ex_(std::move(ex))
183  
        : ex_(std::move(ex))
198  
        , st_(std::move(st))
184  
        , st_(std::move(st))
199  
        , inner_(std::move(inner))
185  
        , inner_(std::move(inner))
200  
    {
186  
    {
201  
    }
187  
    }
202  

188  

203  
    // with allocator, inherit stop token (use template to avoid void parameter)
189  
    // with allocator, inherit stop token (use template to avoid void parameter)
204  
    template<class A>
190  
    template<class A>
205  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
191  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
206  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
192  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
207  
        : ex_(std::move(ex))
193  
        : ex_(std::move(ex))
208  
        , resource_(std::move(alloc))
194  
        , resource_(std::move(alloc))
209  
        , inner_(std::move(inner))
195  
        , inner_(std::move(inner))
210  
    {
196  
    {
211  
    }
197  
    }
212  

198  

213  
    // with allocator, explicit stop token (use template to avoid void parameter)
199  
    // with allocator, explicit stop token (use template to avoid void parameter)
214  
    template<class A>
200  
    template<class A>
215  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
201  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
216  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
202  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
217  
        : ex_(std::move(ex))
203  
        : ex_(std::move(ex))
218  
        , resource_(std::move(alloc))
204  
        , resource_(std::move(alloc))
219  
        , st_(std::move(st))
205  
        , st_(std::move(st))
220  
        , inner_(std::move(inner))
206  
        , inner_(std::move(inner))
221  
    {
207  
    {
222  
    }
208  
    }
223  

209  

224  
    bool await_ready() const noexcept
210  
    bool await_ready() const noexcept
225  
    {
211  
    {
226  
        return inner_.await_ready();
212  
        return inner_.await_ready();
227  
    }
213  
    }
228  

214  

229  
    decltype(auto) await_resume()
215  
    decltype(auto) await_resume()
230  
    {
216  
    {
231  
        return inner_.await_resume();
217  
        return inner_.await_resume();
232  
    }
218  
    }
233  

219  

234  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
220  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
235  
    {
221  
    {
236  
        tr_ = make_dispatch_trampoline();
222  
        tr_ = make_dispatch_trampoline();
237  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
223  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
238  
        tr_.h_.promise().parent_ = cont;
224  
        tr_.h_.promise().parent_ = cont;
239  

225  

240  
        auto h = inner_.handle();
226  
        auto h = inner_.handle();
241  
        auto& p = h.promise();
227  
        auto& p = h.promise();
242  
        p.set_continuation(tr_.h_);
228  
        p.set_continuation(tr_.h_);
243  

229  

244  
        env_.executor = ex_;
230  
        env_.executor = ex_;
245  
        if constexpr (InheritStopToken)
231  
        if constexpr (InheritStopToken)
246  
            env_.stop_token = caller_env->stop_token;
232  
            env_.stop_token = caller_env->stop_token;
247  
        else
233  
        else
248  
            env_.stop_token = st_;
234  
            env_.stop_token = st_;
249  

235  

250  
        if constexpr (!std::is_void_v<Alloc>)
236  
        if constexpr (!std::is_void_v<Alloc>)
251  
            env_.frame_allocator = resource_.get();
237  
            env_.frame_allocator = resource_.get();
252  
        else
238  
        else
253  
            env_.frame_allocator = caller_env->frame_allocator;
239  
            env_.frame_allocator = caller_env->frame_allocator;
254  

240  

255  
        p.set_environment(&env_);
241  
        p.set_environment(&env_);
256  
        return h;
242  
        return h;
257  
    }
243  
    }
258  

244  

259  
    // Non-copyable
245  
    // Non-copyable
260  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
246  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
261  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
247  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
262  

248  

263  
    // Movable (no noexcept - Task may throw)
249  
    // Movable (no noexcept - Task may throw)
264  
    run_awaitable_ex(run_awaitable_ex&&) = default;
250  
    run_awaitable_ex(run_awaitable_ex&&) = default;
265  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
251  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
266  
};
252  
};
267 -
//----------------------------------------------------------
 
268 -
//
 
269 -
// run_awaitable - no executor (inherits caller's executor)
 
270 -
//
 
271 -
//----------------------------------------------------------
 
272 -

 
273  

253  

274  
/** Awaitable that runs a task with optional stop_token override.
254  
/** Awaitable that runs a task with optional stop_token override.
275  

255  

276  
    Does NOT store an executor - the task inherits the caller's executor
256  
    Does NOT store an executor - the task inherits the caller's executor
277  
    directly. Executors always match, so no dispatch trampoline is needed.
257  
    directly. Executors always match, so no dispatch trampoline is needed.
278  
    The inner task's `final_suspend` resumes the parent directly via
258  
    The inner task's `final_suspend` resumes the parent directly via
279  
    unconditional symmetric transfer.
259  
    unconditional symmetric transfer.
280  

260  

281  
    @tparam Task The IoRunnable type
261  
    @tparam Task The IoRunnable type
282  
    @tparam InheritStopToken If true, inherit caller's stop token
262  
    @tparam InheritStopToken If true, inherit caller's stop token
283  
    @tparam Alloc The allocator type (void for no allocator)
263  
    @tparam Alloc The allocator type (void for no allocator)
284  
*/
264  
*/
285  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
265  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
286  
struct [[nodiscard]] run_awaitable
266  
struct [[nodiscard]] run_awaitable
287  
{
267  
{
288  
    frame_memory_resource<Alloc> resource_;
268  
    frame_memory_resource<Alloc> resource_;
289  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
269  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
290  
    io_env env_;
270  
    io_env env_;
291  
    Task inner_;  // Last: destroyed first, while env_ is still valid
271  
    Task inner_;  // Last: destroyed first, while env_ is still valid
292  

272  

293  
    // void allocator, inherit stop token
273  
    // void allocator, inherit stop token
294  
    explicit run_awaitable(Task inner)
274  
    explicit run_awaitable(Task inner)
295  
        requires (InheritStopToken && std::is_void_v<Alloc>)
275  
        requires (InheritStopToken && std::is_void_v<Alloc>)
296  
        : inner_(std::move(inner))
276  
        : inner_(std::move(inner))
297  
    {
277  
    {
298  
    }
278  
    }
299  

279  

300  
    // void allocator, explicit stop token
280  
    // void allocator, explicit stop token
301  
    run_awaitable(Task inner, std::stop_token st)
281  
    run_awaitable(Task inner, std::stop_token st)
302  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
282  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
303  
        : st_(std::move(st))
283  
        : st_(std::move(st))
304  
        , inner_(std::move(inner))
284  
        , inner_(std::move(inner))
305  
    {
285  
    {
306  
    }
286  
    }
307  

287  

308  
    // with allocator, inherit stop token (use template to avoid void parameter)
288  
    // with allocator, inherit stop token (use template to avoid void parameter)
309  
    template<class A>
289  
    template<class A>
310  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
290  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
311  
    run_awaitable(A alloc, Task inner)
291  
    run_awaitable(A alloc, Task inner)
312  
        : resource_(std::move(alloc))
292  
        : resource_(std::move(alloc))
313  
        , inner_(std::move(inner))
293  
        , inner_(std::move(inner))
314  
    {
294  
    {
315  
    }
295  
    }
316  

296  

317  
    // with allocator, explicit stop token (use template to avoid void parameter)
297  
    // with allocator, explicit stop token (use template to avoid void parameter)
318  
    template<class A>
298  
    template<class A>
319  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
299  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
320  
    run_awaitable(A alloc, Task inner, std::stop_token st)
300  
    run_awaitable(A alloc, Task inner, std::stop_token st)
321  
        : resource_(std::move(alloc))
301  
        : resource_(std::move(alloc))
322  
        , st_(std::move(st))
302  
        , st_(std::move(st))
323  
        , inner_(std::move(inner))
303  
        , inner_(std::move(inner))
324  
    {
304  
    {
325  
    }
305  
    }
326  

306  

327  
    bool await_ready() const noexcept
307  
    bool await_ready() const noexcept
328  
    {
308  
    {
329  
        return inner_.await_ready();
309  
        return inner_.await_ready();
330  
    }
310  
    }
331  

311  

332  
    decltype(auto) await_resume()
312  
    decltype(auto) await_resume()
333  
    {
313  
    {
334  
        return inner_.await_resume();
314  
        return inner_.await_resume();
335  
    }
315  
    }
336  

316  

337  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
317  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
338  
    {
318  
    {
339  
        auto h = inner_.handle();
319  
        auto h = inner_.handle();
340  
        auto& p = h.promise();
320  
        auto& p = h.promise();
341  
        p.set_continuation(cont);
321  
        p.set_continuation(cont);
342  

322  

343  
        env_.executor = caller_env->executor;
323  
        env_.executor = caller_env->executor;
344  
        if constexpr (InheritStopToken)
324  
        if constexpr (InheritStopToken)
345  
            env_.stop_token = caller_env->stop_token;
325  
            env_.stop_token = caller_env->stop_token;
346  
        else
326  
        else
347  
            env_.stop_token = st_;
327  
            env_.stop_token = st_;
348  

328  

349  
        if constexpr (!std::is_void_v<Alloc>)
329  
        if constexpr (!std::is_void_v<Alloc>)
350  
            env_.frame_allocator = resource_.get();
330  
            env_.frame_allocator = resource_.get();
351  
        else
331  
        else
352  
            env_.frame_allocator = caller_env->frame_allocator;
332  
            env_.frame_allocator = caller_env->frame_allocator;
353  

333  

354  
        p.set_environment(&env_);
334  
        p.set_environment(&env_);
355  
        return h;
335  
        return h;
356  
    }
336  
    }
357  

337  

358  
    // Non-copyable
338  
    // Non-copyable
359  
    run_awaitable(run_awaitable const&) = delete;
339  
    run_awaitable(run_awaitable const&) = delete;
360  
    run_awaitable& operator=(run_awaitable const&) = delete;
340  
    run_awaitable& operator=(run_awaitable const&) = delete;
361  

341  

362  
    // Movable (no noexcept - Task may throw)
342  
    // Movable (no noexcept - Task may throw)
363  
    run_awaitable(run_awaitable&&) = default;
343  
    run_awaitable(run_awaitable&&) = default;
364  
    run_awaitable& operator=(run_awaitable&&) = default;
344  
    run_awaitable& operator=(run_awaitable&&) = default;
365  
};
345  
};
366 -
//----------------------------------------------------------
 
367 -
//
 
368 -
// run_wrapper_ex - with executor
 
369 -
//
 
370 -
//----------------------------------------------------------
 
371 -

 
372  

346  

373  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
347  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
374  

348  

375  
    @tparam Ex The executor type.
349  
    @tparam Ex The executor type.
376  
    @tparam InheritStopToken If true, inherit caller's stop token.
350  
    @tparam InheritStopToken If true, inherit caller's stop token.
377  
    @tparam Alloc The allocator type (void for no allocator).
351  
    @tparam Alloc The allocator type (void for no allocator).
378  
*/
352  
*/
379  
template<Executor Ex, bool InheritStopToken, class Alloc>
353  
template<Executor Ex, bool InheritStopToken, class Alloc>
380  
class [[nodiscard]] run_wrapper_ex
354  
class [[nodiscard]] run_wrapper_ex
381  
{
355  
{
382  
    Ex ex_;
356  
    Ex ex_;
383  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
357  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
384  
    frame_memory_resource<Alloc> resource_;
358  
    frame_memory_resource<Alloc> resource_;
385  
    Alloc alloc_;  // Copy to pass to awaitable
359  
    Alloc alloc_;  // Copy to pass to awaitable
386  

360  

387  
public:
361  
public:
388  
    run_wrapper_ex(Ex ex, Alloc alloc)
362  
    run_wrapper_ex(Ex ex, Alloc alloc)
389  
        requires InheritStopToken
363  
        requires InheritStopToken
390  
        : ex_(std::move(ex))
364  
        : ex_(std::move(ex))
391  
        , resource_(alloc)
365  
        , resource_(alloc)
392  
        , alloc_(std::move(alloc))
366  
        , alloc_(std::move(alloc))
393  
    {
367  
    {
394  
        set_current_frame_allocator(&resource_);
368  
        set_current_frame_allocator(&resource_);
395  
    }
369  
    }
396  

370  

397  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
371  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
398  
        requires (!InheritStopToken)
372  
        requires (!InheritStopToken)
399  
        : ex_(std::move(ex))
373  
        : ex_(std::move(ex))
400  
        , st_(std::move(st))
374  
        , st_(std::move(st))
401  
        , resource_(alloc)
375  
        , resource_(alloc)
402  
        , alloc_(std::move(alloc))
376  
        , alloc_(std::move(alloc))
403  
    {
377  
    {
404  
        set_current_frame_allocator(&resource_);
378  
        set_current_frame_allocator(&resource_);
405  
    }
379  
    }
406  

380  

407  
    // Non-copyable, non-movable (must be used immediately)
381  
    // Non-copyable, non-movable (must be used immediately)
408  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
382  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
409  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
383  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
410  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
384  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
411  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
385  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
412  

386  

413  
    template<IoRunnable Task>
387  
    template<IoRunnable Task>
414  
    [[nodiscard]] auto operator()(Task t) &&
388  
    [[nodiscard]] auto operator()(Task t) &&
415  
    {
389  
    {
416  
        if constexpr (InheritStopToken)
390  
        if constexpr (InheritStopToken)
417  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
391  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
418  
                std::move(ex_), std::move(alloc_), std::move(t)};
392  
                std::move(ex_), std::move(alloc_), std::move(t)};
419  
        else
393  
        else
420  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
394  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
421  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
395  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
422  
    }
396  
    }
423  
};
397  
};
424  

398  

425  
/// Specialization for memory_resource* - stores pointer directly.
399  
/// Specialization for memory_resource* - stores pointer directly.
426  
template<Executor Ex, bool InheritStopToken>
400  
template<Executor Ex, bool InheritStopToken>
427  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
401  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
428  
{
402  
{
429  
    Ex ex_;
403  
    Ex ex_;
430  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
404  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
431  
    std::pmr::memory_resource* mr_;
405  
    std::pmr::memory_resource* mr_;
432  

406  

433  
public:
407  
public:
434  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
408  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
435  
        requires InheritStopToken
409  
        requires InheritStopToken
436  
        : ex_(std::move(ex))
410  
        : ex_(std::move(ex))
437  
        , mr_(mr)
411  
        , mr_(mr)
438  
    {
412  
    {
439  
        set_current_frame_allocator(mr_);
413  
        set_current_frame_allocator(mr_);
440  
    }
414  
    }
441  

415  

442  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
416  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
443  
        requires (!InheritStopToken)
417  
        requires (!InheritStopToken)
444  
        : ex_(std::move(ex))
418  
        : ex_(std::move(ex))
445  
        , st_(std::move(st))
419  
        , st_(std::move(st))
446  
        , mr_(mr)
420  
        , mr_(mr)
447  
    {
421  
    {
448  
        set_current_frame_allocator(mr_);
422  
        set_current_frame_allocator(mr_);
449  
    }
423  
    }
450  

424  

451  
    // Non-copyable, non-movable (must be used immediately)
425  
    // Non-copyable, non-movable (must be used immediately)
452  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
426  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
453  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
427  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
454  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
428  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
455  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
429  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
456  

430  

457  
    template<IoRunnable Task>
431  
    template<IoRunnable Task>
458  
    [[nodiscard]] auto operator()(Task t) &&
432  
    [[nodiscard]] auto operator()(Task t) &&
459  
    {
433  
    {
460  
        if constexpr (InheritStopToken)
434  
        if constexpr (InheritStopToken)
461  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
435  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
462  
                std::move(ex_), mr_, std::move(t)};
436  
                std::move(ex_), mr_, std::move(t)};
463  
        else
437  
        else
464  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
438  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
465  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
439  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
466  
    }
440  
    }
467  
};
441  
};
468  

442  

469  
/// Specialization for no allocator (void).
443  
/// Specialization for no allocator (void).
470  
template<Executor Ex, bool InheritStopToken>
444  
template<Executor Ex, bool InheritStopToken>
471  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
445  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
472  
{
446  
{
473  
    Ex ex_;
447  
    Ex ex_;
474  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
448  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
475  

449  

476  
public:
450  
public:
477  
    explicit run_wrapper_ex(Ex ex)
451  
    explicit run_wrapper_ex(Ex ex)
478  
        requires InheritStopToken
452  
        requires InheritStopToken
479  
        : ex_(std::move(ex))
453  
        : ex_(std::move(ex))
480  
    {
454  
    {
481  
    }
455  
    }
482  

456  

483  
    run_wrapper_ex(Ex ex, std::stop_token st)
457  
    run_wrapper_ex(Ex ex, std::stop_token st)
484  
        requires (!InheritStopToken)
458  
        requires (!InheritStopToken)
485  
        : ex_(std::move(ex))
459  
        : ex_(std::move(ex))
486  
        , st_(std::move(st))
460  
        , st_(std::move(st))
487  
    {
461  
    {
488  
    }
462  
    }
489  

463  

490  
    // Non-copyable, non-movable (must be used immediately)
464  
    // Non-copyable, non-movable (must be used immediately)
491  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
465  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
492  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
466  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
493  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
467  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
494  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
468  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
495  

469  

496  
    template<IoRunnable Task>
470  
    template<IoRunnable Task>
497  
    [[nodiscard]] auto operator()(Task t) &&
471  
    [[nodiscard]] auto operator()(Task t) &&
498  
    {
472  
    {
499  
        if constexpr (InheritStopToken)
473  
        if constexpr (InheritStopToken)
500  
            return run_awaitable_ex<Task, Ex, true>{
474  
            return run_awaitable_ex<Task, Ex, true>{
501  
                std::move(ex_), std::move(t)};
475  
                std::move(ex_), std::move(t)};
502  
        else
476  
        else
503  
            return run_awaitable_ex<Task, Ex, false>{
477  
            return run_awaitable_ex<Task, Ex, false>{
504  
                std::move(ex_), std::move(t), std::move(st_)};
478  
                std::move(ex_), std::move(t), std::move(st_)};
505  
    }
479  
    }
506  
};
480  
};
507 -
//----------------------------------------------------------
 
508 -
//
 
509 -
// run_wrapper - no executor (inherits caller's executor)
 
510 -
//
 
511 -
//----------------------------------------------------------
 
512 -

 
513  

481  

514  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
482  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
515  

483  

516  
    @tparam InheritStopToken If true, inherit caller's stop token.
484  
    @tparam InheritStopToken If true, inherit caller's stop token.
517  
    @tparam Alloc The allocator type (void for no allocator).
485  
    @tparam Alloc The allocator type (void for no allocator).
518  
*/
486  
*/
519  
template<bool InheritStopToken, class Alloc>
487  
template<bool InheritStopToken, class Alloc>
520  
class [[nodiscard]] run_wrapper
488  
class [[nodiscard]] run_wrapper
521  
{
489  
{
522  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
490  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
523  
    frame_memory_resource<Alloc> resource_;
491  
    frame_memory_resource<Alloc> resource_;
524  
    Alloc alloc_;  // Copy to pass to awaitable
492  
    Alloc alloc_;  // Copy to pass to awaitable
525  

493  

526  
public:
494  
public:
527  
    explicit run_wrapper(Alloc alloc)
495  
    explicit run_wrapper(Alloc alloc)
528  
        requires InheritStopToken
496  
        requires InheritStopToken
529  
        : resource_(alloc)
497  
        : resource_(alloc)
530  
        , alloc_(std::move(alloc))
498  
        , alloc_(std::move(alloc))
531  
    {
499  
    {
532  
        set_current_frame_allocator(&resource_);
500  
        set_current_frame_allocator(&resource_);
533  
    }
501  
    }
534  

502  

535  
    run_wrapper(std::stop_token st, Alloc alloc)
503  
    run_wrapper(std::stop_token st, Alloc alloc)
536  
        requires (!InheritStopToken)
504  
        requires (!InheritStopToken)
537  
        : st_(std::move(st))
505  
        : st_(std::move(st))
538  
        , resource_(alloc)
506  
        , resource_(alloc)
539  
        , alloc_(std::move(alloc))
507  
        , alloc_(std::move(alloc))
540  
    {
508  
    {
541  
        set_current_frame_allocator(&resource_);
509  
        set_current_frame_allocator(&resource_);
542  
    }
510  
    }
543  

511  

544  
    // Non-copyable, non-movable (must be used immediately)
512  
    // Non-copyable, non-movable (must be used immediately)
545  
    run_wrapper(run_wrapper const&) = delete;
513  
    run_wrapper(run_wrapper const&) = delete;
546  
    run_wrapper(run_wrapper&&) = delete;
514  
    run_wrapper(run_wrapper&&) = delete;
547  
    run_wrapper& operator=(run_wrapper const&) = delete;
515  
    run_wrapper& operator=(run_wrapper const&) = delete;
548  
    run_wrapper& operator=(run_wrapper&&) = delete;
516  
    run_wrapper& operator=(run_wrapper&&) = delete;
549  

517  

550  
    template<IoRunnable Task>
518  
    template<IoRunnable Task>
551  
    [[nodiscard]] auto operator()(Task t) &&
519  
    [[nodiscard]] auto operator()(Task t) &&
552  
    {
520  
    {
553  
        if constexpr (InheritStopToken)
521  
        if constexpr (InheritStopToken)
554  
            return run_awaitable<Task, true, Alloc>{
522  
            return run_awaitable<Task, true, Alloc>{
555  
                std::move(alloc_), std::move(t)};
523  
                std::move(alloc_), std::move(t)};
556  
        else
524  
        else
557  
            return run_awaitable<Task, false, Alloc>{
525  
            return run_awaitable<Task, false, Alloc>{
558  
                std::move(alloc_), std::move(t), std::move(st_)};
526  
                std::move(alloc_), std::move(t), std::move(st_)};
559  
    }
527  
    }
560  
};
528  
};
561  

529  

562  
/// Specialization for memory_resource* - stores pointer directly.
530  
/// Specialization for memory_resource* - stores pointer directly.
563  
template<bool InheritStopToken>
531  
template<bool InheritStopToken>
564  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
532  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
565  
{
533  
{
566  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
534  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
567  
    std::pmr::memory_resource* mr_;
535  
    std::pmr::memory_resource* mr_;
568  

536  

569  
public:
537  
public:
570  
    explicit run_wrapper(std::pmr::memory_resource* mr)
538  
    explicit run_wrapper(std::pmr::memory_resource* mr)
571  
        requires InheritStopToken
539  
        requires InheritStopToken
572  
        : mr_(mr)
540  
        : mr_(mr)
573  
    {
541  
    {
574  
        set_current_frame_allocator(mr_);
542  
        set_current_frame_allocator(mr_);
575  
    }
543  
    }
576  

544  

577  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
545  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
578  
        requires (!InheritStopToken)
546  
        requires (!InheritStopToken)
579  
        : st_(std::move(st))
547  
        : st_(std::move(st))
580  
        , mr_(mr)
548  
        , mr_(mr)
581  
    {
549  
    {
582  
        set_current_frame_allocator(mr_);
550  
        set_current_frame_allocator(mr_);
583  
    }
551  
    }
584  

552  

585  
    // Non-copyable, non-movable (must be used immediately)
553  
    // Non-copyable, non-movable (must be used immediately)
586  
    run_wrapper(run_wrapper const&) = delete;
554  
    run_wrapper(run_wrapper const&) = delete;
587  
    run_wrapper(run_wrapper&&) = delete;
555  
    run_wrapper(run_wrapper&&) = delete;
588  
    run_wrapper& operator=(run_wrapper const&) = delete;
556  
    run_wrapper& operator=(run_wrapper const&) = delete;
589  
    run_wrapper& operator=(run_wrapper&&) = delete;
557  
    run_wrapper& operator=(run_wrapper&&) = delete;
590  

558  

591  
    template<IoRunnable Task>
559  
    template<IoRunnable Task>
592  
    [[nodiscard]] auto operator()(Task t) &&
560  
    [[nodiscard]] auto operator()(Task t) &&
593  
    {
561  
    {
594  
        if constexpr (InheritStopToken)
562  
        if constexpr (InheritStopToken)
595  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
563  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
596  
                mr_, std::move(t)};
564  
                mr_, std::move(t)};
597  
        else
565  
        else
598  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
566  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
599  
                mr_, std::move(t), std::move(st_)};
567  
                mr_, std::move(t), std::move(st_)};
600  
    }
568  
    }
601  
};
569  
};
602  

570  

603  
/// Specialization for stop_token only (no allocator).
571  
/// Specialization for stop_token only (no allocator).
604  
template<>
572  
template<>
605  
class [[nodiscard]] run_wrapper<false, void>
573  
class [[nodiscard]] run_wrapper<false, void>
606  
{
574  
{
607  
    std::stop_token st_;
575  
    std::stop_token st_;
608  

576  

609  
public:
577  
public:
610  
    explicit run_wrapper(std::stop_token st)
578  
    explicit run_wrapper(std::stop_token st)
611  
        : st_(std::move(st))
579  
        : st_(std::move(st))
612  
    {
580  
    {
613  
    }
581  
    }
614  

582  

615  
    // Non-copyable, non-movable (must be used immediately)
583  
    // Non-copyable, non-movable (must be used immediately)
616  
    run_wrapper(run_wrapper const&) = delete;
584  
    run_wrapper(run_wrapper const&) = delete;
617  
    run_wrapper(run_wrapper&&) = delete;
585  
    run_wrapper(run_wrapper&&) = delete;
618  
    run_wrapper& operator=(run_wrapper const&) = delete;
586  
    run_wrapper& operator=(run_wrapper const&) = delete;
619  
    run_wrapper& operator=(run_wrapper&&) = delete;
587  
    run_wrapper& operator=(run_wrapper&&) = delete;
620  

588  

621  
    template<IoRunnable Task>
589  
    template<IoRunnable Task>
622  
    [[nodiscard]] auto operator()(Task t) &&
590  
    [[nodiscard]] auto operator()(Task t) &&
623  
    {
591  
    {
624  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
592  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
625  
    }
593  
    }
626  
};
594  
};
627  

595  

628  
} // namespace boost::capy::detail
596  
} // namespace boost::capy::detail
629  

597  

630  
namespace boost::capy {
598  
namespace boost::capy {
631 -
//----------------------------------------------------------
 
632 -
//
 
633 -
// run() overloads - with executor
 
634 -
//
 
635 -
//----------------------------------------------------------
 
636 -

 
637  

599  

638  
/** Bind a task to execute on a specific executor.
600  
/** Bind a task to execute on a specific executor.
639  

601  

640  
    Returns a wrapper that accepts a task and produces an awaitable.
602  
    Returns a wrapper that accepts a task and produces an awaitable.
641  
    When co_awaited, the task runs on the specified executor.
603  
    When co_awaited, the task runs on the specified executor.
642  

604  

643  
    @par Example
605  
    @par Example
644  
    @code
606  
    @code
645  
    co_await run(other_executor)(my_task());
607  
    co_await run(other_executor)(my_task());
646  
    @endcode
608  
    @endcode
647  

609  

648  
    @param ex The executor on which the task should run.
610  
    @param ex The executor on which the task should run.
649  

611  

650  
    @return A wrapper that accepts a task for execution.
612  
    @return A wrapper that accepts a task for execution.
651  

613  

652  
    @see task
614  
    @see task
653  
    @see executor
615  
    @see executor
654  
*/
616  
*/
655  
template<Executor Ex>
617  
template<Executor Ex>
656  
[[nodiscard]] auto
618  
[[nodiscard]] auto
657  
run(Ex ex)
619  
run(Ex ex)
658  
{
620  
{
659  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
621  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
660  
}
622  
}
661  

623  

662  
/** Bind a task to an executor with a stop token.
624  
/** Bind a task to an executor with a stop token.
663  

625  

664  
    @param ex The executor on which the task should run.
626  
    @param ex The executor on which the task should run.
665  
    @param st The stop token for cooperative cancellation.
627  
    @param st The stop token for cooperative cancellation.
666  

628  

667  
    @return A wrapper that accepts a task for execution.
629  
    @return A wrapper that accepts a task for execution.
668  
*/
630  
*/
669  
template<Executor Ex>
631  
template<Executor Ex>
670  
[[nodiscard]] auto
632  
[[nodiscard]] auto
671  
run(Ex ex, std::stop_token st)
633  
run(Ex ex, std::stop_token st)
672  
{
634  
{
673  
    return detail::run_wrapper_ex<Ex, false, void>{
635  
    return detail::run_wrapper_ex<Ex, false, void>{
674  
        std::move(ex), std::move(st)};
636  
        std::move(ex), std::move(st)};
675  
}
637  
}
676  

638  

677  
/** Bind a task to an executor with a memory resource.
639  
/** Bind a task to an executor with a memory resource.
678  

640  

679  
    @param ex The executor on which the task should run.
641  
    @param ex The executor on which the task should run.
680  
    @param mr The memory resource for frame allocation.
642  
    @param mr The memory resource for frame allocation.
681  

643  

682  
    @return A wrapper that accepts a task for execution.
644  
    @return A wrapper that accepts a task for execution.
683  
*/
645  
*/
684  
template<Executor Ex>
646  
template<Executor Ex>
685  
[[nodiscard]] auto
647  
[[nodiscard]] auto
686  
run(Ex ex, std::pmr::memory_resource* mr)
648  
run(Ex ex, std::pmr::memory_resource* mr)
687  
{
649  
{
688  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
650  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
689  
        std::move(ex), mr};
651  
        std::move(ex), mr};
690  
}
652  
}
691  

653  

692  
/** Bind a task to an executor with a standard allocator.
654  
/** Bind a task to an executor with a standard allocator.
693  

655  

694  
    @param ex The executor on which the task should run.
656  
    @param ex The executor on which the task should run.
695  
    @param alloc The allocator for frame allocation.
657  
    @param alloc The allocator for frame allocation.
696  

658  

697  
    @return A wrapper that accepts a task for execution.
659  
    @return A wrapper that accepts a task for execution.
698  
*/
660  
*/
699  
template<Executor Ex, detail::Allocator Alloc>
661  
template<Executor Ex, detail::Allocator Alloc>
700  
[[nodiscard]] auto
662  
[[nodiscard]] auto
701  
run(Ex ex, Alloc alloc)
663  
run(Ex ex, Alloc alloc)
702  
{
664  
{
703  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
665  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
704  
        std::move(ex), std::move(alloc)};
666  
        std::move(ex), std::move(alloc)};
705  
}
667  
}
706  

668  

707  
/** Bind a task to an executor with stop token and memory resource.
669  
/** Bind a task to an executor with stop token and memory resource.
708  

670  

709  
    @param ex The executor on which the task should run.
671  
    @param ex The executor on which the task should run.
710  
    @param st The stop token for cooperative cancellation.
672  
    @param st The stop token for cooperative cancellation.
711  
    @param mr The memory resource for frame allocation.
673  
    @param mr The memory resource for frame allocation.
712  

674  

713  
    @return A wrapper that accepts a task for execution.
675  
    @return A wrapper that accepts a task for execution.
714  
*/
676  
*/
715  
template<Executor Ex>
677  
template<Executor Ex>
716  
[[nodiscard]] auto
678  
[[nodiscard]] auto
717  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
679  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
718  
{
680  
{
719  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
681  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
720  
        std::move(ex), std::move(st), mr};
682  
        std::move(ex), std::move(st), mr};
721  
}
683  
}
722  

684  

723  
/** Bind a task to an executor with stop token and standard allocator.
685  
/** Bind a task to an executor with stop token and standard allocator.
724  

686  

725  
    @param ex The executor on which the task should run.
687  
    @param ex The executor on which the task should run.
726  
    @param st The stop token for cooperative cancellation.
688  
    @param st The stop token for cooperative cancellation.
727  
    @param alloc The allocator for frame allocation.
689  
    @param alloc The allocator for frame allocation.
728  

690  

729  
    @return A wrapper that accepts a task for execution.
691  
    @return A wrapper that accepts a task for execution.
730  
*/
692  
*/
731  
template<Executor Ex, detail::Allocator Alloc>
693  
template<Executor Ex, detail::Allocator Alloc>
732  
[[nodiscard]] auto
694  
[[nodiscard]] auto
733  
run(Ex ex, std::stop_token st, Alloc alloc)
695  
run(Ex ex, std::stop_token st, Alloc alloc)
734  
{
696  
{
735  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
697  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
736  
        std::move(ex), std::move(st), std::move(alloc)};
698  
        std::move(ex), std::move(st), std::move(alloc)};
737 -

 
738 -
//----------------------------------------------------------
 
739 -
//
 
740 -
// run() overloads - no executor (inherits caller's)
 
741 -
//
 
742 -
//----------------------------------------------------------
 
743  
}
699  
}
744  

700  

745  
/** Run a task with a custom stop token.
701  
/** Run a task with a custom stop token.
746  

702  

747  
    The task inherits the caller's executor. Only the stop token
703  
    The task inherits the caller's executor. Only the stop token
748  
    is overridden.
704  
    is overridden.
749  

705  

750  
    @par Example
706  
    @par Example
751  
    @code
707  
    @code
752  
    std::stop_source source;
708  
    std::stop_source source;
753  
    co_await run(source.get_token())(cancellable_task());
709  
    co_await run(source.get_token())(cancellable_task());
754  
    @endcode
710  
    @endcode
755  

711  

756  
    @param st The stop token for cooperative cancellation.
712  
    @param st The stop token for cooperative cancellation.
757  

713  

758  
    @return A wrapper that accepts a task for execution.
714  
    @return A wrapper that accepts a task for execution.
759  
*/
715  
*/
760  
[[nodiscard]] inline auto
716  
[[nodiscard]] inline auto
761  
run(std::stop_token st)
717  
run(std::stop_token st)
762  
{
718  
{
763  
    return detail::run_wrapper<false, void>{std::move(st)};
719  
    return detail::run_wrapper<false, void>{std::move(st)};
764  
}
720  
}
765  

721  

766  
/** Run a task with a custom memory resource.
722  
/** Run a task with a custom memory resource.
767  

723  

768  
    The task inherits the caller's executor. The memory resource
724  
    The task inherits the caller's executor. The memory resource
769  
    is used for nested frame allocations.
725  
    is used for nested frame allocations.
770  

726  

771  
    @param mr The memory resource for frame allocation.
727  
    @param mr The memory resource for frame allocation.
772  

728  

773  
    @return A wrapper that accepts a task for execution.
729  
    @return A wrapper that accepts a task for execution.
774  
*/
730  
*/
775  
[[nodiscard]] inline auto
731  
[[nodiscard]] inline auto
776  
run(std::pmr::memory_resource* mr)
732  
run(std::pmr::memory_resource* mr)
777  
{
733  
{
778  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
734  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
779  
}
735  
}
780  

736  

781  
/** Run a task with a custom standard allocator.
737  
/** Run a task with a custom standard allocator.
782  

738  

783  
    The task inherits the caller's executor. The allocator is used
739  
    The task inherits the caller's executor. The allocator is used
784  
    for nested frame allocations.
740  
    for nested frame allocations.
785  

741  

786  
    @param alloc The allocator for frame allocation.
742  
    @param alloc The allocator for frame allocation.
787  

743  

788  
    @return A wrapper that accepts a task for execution.
744  
    @return A wrapper that accepts a task for execution.
789  
*/
745  
*/
790  
template<detail::Allocator Alloc>
746  
template<detail::Allocator Alloc>
791  
[[nodiscard]] auto
747  
[[nodiscard]] auto
792  
run(Alloc alloc)
748  
run(Alloc alloc)
793  
{
749  
{
794  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
750  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
795  
}
751  
}
796  

752  

797  
/** Run a task with stop token and memory resource.
753  
/** Run a task with stop token and memory resource.
798  

754  

799  
    The task inherits the caller's executor.
755  
    The task inherits the caller's executor.
800  

756  

801  
    @param st The stop token for cooperative cancellation.
757  
    @param st The stop token for cooperative cancellation.
802  
    @param mr The memory resource for frame allocation.
758  
    @param mr The memory resource for frame allocation.
803  

759  

804  
    @return A wrapper that accepts a task for execution.
760  
    @return A wrapper that accepts a task for execution.
805  
*/
761  
*/
806  
[[nodiscard]] inline auto
762  
[[nodiscard]] inline auto
807  
run(std::stop_token st, std::pmr::memory_resource* mr)
763  
run(std::stop_token st, std::pmr::memory_resource* mr)
808  
{
764  
{
809  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
765  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
810  
        std::move(st), mr};
766  
        std::move(st), mr};
811  
}
767  
}
812  

768  

813  
/** Run a task with stop token and standard allocator.
769  
/** Run a task with stop token and standard allocator.
814  

770  

815  
    The task inherits the caller's executor.
771  
    The task inherits the caller's executor.
816  

772  

817  
    @param st The stop token for cooperative cancellation.
773  
    @param st The stop token for cooperative cancellation.
818  
    @param alloc The allocator for frame allocation.
774  
    @param alloc The allocator for frame allocation.
819  

775  

820  
    @return A wrapper that accepts a task for execution.
776  
    @return A wrapper that accepts a task for execution.
821  
*/
777  
*/
822  
template<detail::Allocator Alloc>
778  
template<detail::Allocator Alloc>
823  
[[nodiscard]] auto
779  
[[nodiscard]] auto
824  
run(std::stop_token st, Alloc alloc)
780  
run(std::stop_token st, Alloc alloc)
825  
{
781  
{
826  
    return detail::run_wrapper<false, Alloc>{
782  
    return detail::run_wrapper<false, Alloc>{
827  
        std::move(st), std::move(alloc)};
783  
        std::move(st), std::move(alloc)};
828  
}
784  
}
829  

785  

830  
} // namespace boost::capy
786  
} // namespace boost::capy
831  

787  

832  
#endif
788  
#endif