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/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/run.hpp>
14  
#include <boost/capy/detail/run.hpp>
15  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/executor.hpp>
16 -
#include <boost/capy/concept/io_launchable_task.hpp>
16 +
#include <boost/capy/concept/io_runnable.hpp>
 
17 +
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <coroutine>
18  
#include <coroutine>
18  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
20  

21  

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

27  

27  
/*
28  
/*
28  
    Allocator Lifetime Strategy
29  
    Allocator Lifetime Strategy
29  
    ===========================
30  
    ===========================
30  

31  

31  
    When using run() with a custom allocator:
32  
    When using run() with a custom allocator:
32  

33  

33  
        co_await run(ex, alloc)(my_task());
34  
        co_await run(ex, alloc)(my_task());
34  

35  

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

43  

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

47  

47  
    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
48  
    the wrapper). The co_await mechanism extends the awaitable's lifetime
49  
    the wrapper). The co_await mechanism extends the awaitable's lifetime
49  
    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
50  
    saved frame_allocator pointer to point to the awaitable's resource.
51  
    saved frame_allocator pointer to point to the awaitable's resource.
51  

52  

52  
    This works because standard allocator copies are equivalent - memory
53  
    This works because standard allocator copies are equivalent - memory
53  
    allocated with one copy can be deallocated with another copy. The
54  
    allocated with one copy can be deallocated with another copy. The
54  
    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
55  
    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).
56  
*/
57  
*/
57  

58  

58  
namespace boost::capy::detail {
59  
namespace boost::capy::detail {
59  

60  

60  
//----------------------------------------------------------
61  
//----------------------------------------------------------
61  
//
62  
//
 
63 +
// dispatch_trampoline - cross-executor dispatch
 
64 +
//
 
65 +
//----------------------------------------------------------
 
66 +

 
67 +
/** Minimal coroutine that dispatches through the caller's executor.
 
68 +

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

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

 
84 +
        dispatch_trampoline get_return_object() noexcept
 
85 +
        {
 
86 +
            return dispatch_trampoline{
 
87 +
                std::coroutine_handle<promise_type>::from_promise(*this)};
 
88 +
        }
 
89 +

 
90 +
        std::suspend_always initial_suspend() noexcept { return {}; }
 
91 +

 
92 +
        auto final_suspend() noexcept
 
93 +
        {
 
94 +
            struct awaiter
 
95 +
            {
 
96 +
                promise_type* p_;
 
97 +
                bool await_ready() const noexcept { return false; }
 
98 +

 
99 +
                std::coroutine_handle<> await_suspend(
 
100 +
                    std::coroutine_handle<>) noexcept
 
101 +
                {
 
102 +
                    return p_->caller_ex_.dispatch(p_->parent_);
 
103 +
                }
 
104 +

 
105 +
                void await_resume() const noexcept {}
 
106 +
            };
 
107 +
            return awaiter{this};
 
108 +
        }
 
109 +

 
110 +
        void return_void() noexcept {}
 
111 +
        void unhandled_exception() noexcept {}
 
112 +
    };
 
113 +

 
114 +
    std::coroutine_handle<promise_type> h_{nullptr};
 
115 +

 
116 +
    dispatch_trampoline() noexcept = default;
 
117 +

 
118 +
    ~dispatch_trampoline()
 
119 +
    {
 
120 +
        if(h_) h_.destroy();
 
121 +
    }
 
122 +

 
123 +
    dispatch_trampoline(dispatch_trampoline const&) = delete;
 
124 +
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
 
125 +

 
126 +
    dispatch_trampoline(dispatch_trampoline&& o) noexcept
 
127 +
        : h_(std::exchange(o.h_, nullptr)) {}
 
128 +

 
129 +
    dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
 
130 +
    {
 
131 +
        if(this != &o)
 
132 +
        {
 
133 +
            if(h_) h_.destroy();
 
134 +
            h_ = std::exchange(o.h_, nullptr);
 
135 +
        }
 
136 +
        return *this;
 
137 +
    }
 
138 +

 
139 +
private:
 
140 +
    explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
 
141 +
        : h_(h) {}
 
142 +
};
 
143 +

 
144 +
inline dispatch_trampoline make_dispatch_trampoline()
 
145 +
{
 
146 +
    co_return;
 
147 +
}
 
148 +

 
149 +
//----------------------------------------------------------
 
150 +
//
62  
// run_awaitable_ex - with executor (executor switch)
151  
// run_awaitable_ex - with executor (executor switch)
63  
//
152  
//
64  
//----------------------------------------------------------
153  
//----------------------------------------------------------
65  

154  

66 -
/** Awaitable that binds an IoLaunchableTask to a specific executor.
155 +
/** Awaitable that binds an IoRunnable to a specific executor.
67  

156  

68  
    Stores the executor and inner task by value. When co_awaited, the
157  
    Stores the executor and inner task by value. When co_awaited, the
69  
    co_await expression's lifetime extension keeps both alive for the
158  
    co_await expression's lifetime extension keeps both alive for the
70  
    duration of the operation.
159  
    duration of the operation.
71  

160  

 
161 +
    A dispatch trampoline handles the executor switch on completion:
 
162 +
    the inner task's `final_suspend` resumes the trampoline, which
 
163 +
    dispatches back through the caller's executor.
 
164 +

72  
    The `io_env` is owned by this awaitable and is guaranteed to
165  
    The `io_env` is owned by this awaitable and is guaranteed to
73  
    outlive the inner task and all awaitables in its chain. Awaitables
166  
    outlive the inner task and all awaitables in its chain. Awaitables
74  
    may store `io_env const*` without concern for dangling references.
167  
    may store `io_env const*` without concern for dangling references.
75  

168  

76 -
    @tparam Task The IoLaunchableTask type
169 +
    @tparam Task The IoRunnable type
77  
    @tparam Ex The executor type
170  
    @tparam Ex The executor type
78  
    @tparam InheritStopToken If true, inherit caller's stop token
171  
    @tparam InheritStopToken If true, inherit caller's stop token
79  
    @tparam Alloc The allocator type (void for no allocator)
172  
    @tparam Alloc The allocator type (void for no allocator)
80  
*/
173  
*/
81 -
template<IoLaunchableTask Task, Executor Ex, bool InheritStopToken, class Alloc = void>
174 +
template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
82  
struct [[nodiscard]] run_awaitable_ex
175  
struct [[nodiscard]] run_awaitable_ex
83  
{
176  
{
84  
    Ex ex_;
177  
    Ex ex_;
85  
    frame_memory_resource<Alloc> resource_;
178  
    frame_memory_resource<Alloc> resource_;
86  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
179  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
87  
    io_env env_;
180  
    io_env env_;
 
181 +
    dispatch_trampoline tr_;
88  
    Task inner_;  // Last: destroyed first, while env_ is still valid
182  
    Task inner_;  // Last: destroyed first, while env_ is still valid
89  

183  

90  
    // void allocator, inherit stop token
184  
    // void allocator, inherit stop token
91  
    run_awaitable_ex(Ex ex, Task inner)
185  
    run_awaitable_ex(Ex ex, Task inner)
92  
        requires (InheritStopToken && std::is_void_v<Alloc>)
186  
        requires (InheritStopToken && std::is_void_v<Alloc>)
93  
        : ex_(std::move(ex))
187  
        : ex_(std::move(ex))
94  
        , inner_(std::move(inner))
188  
        , inner_(std::move(inner))
95  
    {
189  
    {
96  
    }
190  
    }
97  

191  

98  
    // void allocator, explicit stop token
192  
    // void allocator, explicit stop token
99  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
193  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
100  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
194  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
101  
        : ex_(std::move(ex))
195  
        : ex_(std::move(ex))
102  
        , st_(std::move(st))
196  
        , st_(std::move(st))
103  
        , inner_(std::move(inner))
197  
        , inner_(std::move(inner))
104  
    {
198  
    {
105  
    }
199  
    }
106  

200  

107  
    // with allocator, inherit stop token (use template to avoid void parameter)
201  
    // with allocator, inherit stop token (use template to avoid void parameter)
108  
    template<class A>
202  
    template<class A>
109  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
203  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
110  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
204  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
111  
        : ex_(std::move(ex))
205  
        : ex_(std::move(ex))
112  
        , resource_(std::move(alloc))
206  
        , resource_(std::move(alloc))
113  
        , inner_(std::move(inner))
207  
        , inner_(std::move(inner))
114  
    {
208  
    {
115  
    }
209  
    }
116  

210  

117  
    // with allocator, explicit stop token (use template to avoid void parameter)
211  
    // with allocator, explicit stop token (use template to avoid void parameter)
118  
    template<class A>
212  
    template<class A>
119  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
213  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
120  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
214  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
121  
        : ex_(std::move(ex))
215  
        : ex_(std::move(ex))
122  
        , resource_(std::move(alloc))
216  
        , resource_(std::move(alloc))
123  
        , st_(std::move(st))
217  
        , st_(std::move(st))
124  
        , inner_(std::move(inner))
218  
        , inner_(std::move(inner))
125  
    {
219  
    {
126  
    }
220  
    }
127  

221  

128  
    bool await_ready() const noexcept
222  
    bool await_ready() const noexcept
129  
    {
223  
    {
130  
        return inner_.await_ready();
224  
        return inner_.await_ready();
131  
    }
225  
    }
132  

226  

133  
    decltype(auto) await_resume()
227  
    decltype(auto) await_resume()
134  
    {
228  
    {
135  
        return inner_.await_resume();
229  
        return inner_.await_resume();
136  
    }
230  
    }
137  

231  

138 -
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const& caller_env)
232 +
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
139  
    {
233  
    {
 
234 +
        tr_ = make_dispatch_trampoline();
 
235 +
        tr_.h_.promise().caller_ex_ = caller_env->executor;
 
236 +
        tr_.h_.promise().parent_ = cont;
 
237 +

140  
        auto h = inner_.handle();
238  
        auto h = inner_.handle();
141  
        auto& p = h.promise();
239  
        auto& p = h.promise();
142 -
        p.set_continuation(cont, caller_env.executor);
240 +
        p.set_continuation(tr_.h_);
143  

241  

144  
        env_.executor = ex_;
242  
        env_.executor = ex_;
145  
        if constexpr (InheritStopToken)
243  
        if constexpr (InheritStopToken)
146 -
            env_.stop_token = caller_env.stop_token;
244 +
            env_.stop_token = caller_env->stop_token;
147  
        else
245  
        else
148  
            env_.stop_token = st_;
246  
            env_.stop_token = st_;
149  

247  

150  
        if constexpr (!std::is_void_v<Alloc>)
248  
        if constexpr (!std::is_void_v<Alloc>)
151  
            env_.allocator = resource_.get();
249  
            env_.allocator = resource_.get();
152  
        else
250  
        else
153 -
            env_.allocator = caller_env.allocator;
251 +
            env_.allocator = caller_env->allocator;
154  

252  

155 -
        p.set_environment(env_);
253 +
        p.set_environment(&env_);
156  
        return h;
254  
        return h;
157  
    }
255  
    }
158  

256  

159  
    // Non-copyable
257  
    // Non-copyable
160  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
258  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
161  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
259  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
162  

260  

163  
    // Movable (no noexcept - Task may throw)
261  
    // Movable (no noexcept - Task may throw)
164  
    run_awaitable_ex(run_awaitable_ex&&) = default;
262  
    run_awaitable_ex(run_awaitable_ex&&) = default;
165  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
263  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
166  
};
264  
};
167  

265  

168  
//----------------------------------------------------------
266  
//----------------------------------------------------------
169  
//
267  
//
170  
// run_awaitable - no executor (inherits caller's executor)
268  
// run_awaitable - no executor (inherits caller's executor)
171  
//
269  
//
172  
//----------------------------------------------------------
270  
//----------------------------------------------------------
173  

271  

174  
/** Awaitable that runs a task with optional stop_token override.
272  
/** Awaitable that runs a task with optional stop_token override.
175  

273  

176  
    Does NOT store an executor - the task inherits the caller's executor
274  
    Does NOT store an executor - the task inherits the caller's executor
177 -
    directly. Since executor_ == caller_ex_, complete() does direct
275 +
    directly. Executors always match, so no dispatch trampoline is needed.
178 -
    symmetric transfer without dispatch overhead.
276 +
    The inner task's `final_suspend` resumes the parent directly via
 
277 +
    unconditional symmetric transfer.
179  

278  

180 -
    @tparam Task The IoLaunchableTask type
279 +
    @tparam Task The IoRunnable type
181  
    @tparam InheritStopToken If true, inherit caller's stop token
280  
    @tparam InheritStopToken If true, inherit caller's stop token
182  
    @tparam Alloc The allocator type (void for no allocator)
281  
    @tparam Alloc The allocator type (void for no allocator)
183  
*/
282  
*/
184 -
template<IoLaunchableTask Task, bool InheritStopToken, class Alloc = void>
283 +
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
185  
struct [[nodiscard]] run_awaitable
284  
struct [[nodiscard]] run_awaitable
186  
{
285  
{
187  
    frame_memory_resource<Alloc> resource_;
286  
    frame_memory_resource<Alloc> resource_;
188  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
287  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
189  
    io_env env_;
288  
    io_env env_;
190  
    Task inner_;  // Last: destroyed first, while env_ is still valid
289  
    Task inner_;  // Last: destroyed first, while env_ is still valid
191  

290  

192  
    // void allocator, inherit stop token
291  
    // void allocator, inherit stop token
193  
    explicit run_awaitable(Task inner)
292  
    explicit run_awaitable(Task inner)
194  
        requires (InheritStopToken && std::is_void_v<Alloc>)
293  
        requires (InheritStopToken && std::is_void_v<Alloc>)
195  
        : inner_(std::move(inner))
294  
        : inner_(std::move(inner))
196  
    {
295  
    {
197  
    }
296  
    }
198  

297  

199  
    // void allocator, explicit stop token
298  
    // void allocator, explicit stop token
200  
    run_awaitable(Task inner, std::stop_token st)
299  
    run_awaitable(Task inner, std::stop_token st)
201  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
300  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
202  
        : st_(std::move(st))
301  
        : st_(std::move(st))
203  
        , inner_(std::move(inner))
302  
        , inner_(std::move(inner))
204  
    {
303  
    {
205  
    }
304  
    }
206  

305  

207  
    // with allocator, inherit stop token (use template to avoid void parameter)
306  
    // with allocator, inherit stop token (use template to avoid void parameter)
208  
    template<class A>
307  
    template<class A>
209  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
308  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
210  
    run_awaitable(A alloc, Task inner)
309  
    run_awaitable(A alloc, Task inner)
211  
        : resource_(std::move(alloc))
310  
        : resource_(std::move(alloc))
212  
        , inner_(std::move(inner))
311  
        , inner_(std::move(inner))
213  
    {
312  
    {
214  
    }
313  
    }
215  

314  

216  
    // with allocator, explicit stop token (use template to avoid void parameter)
315  
    // with allocator, explicit stop token (use template to avoid void parameter)
217  
    template<class A>
316  
    template<class A>
218  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
317  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
219  
    run_awaitable(A alloc, Task inner, std::stop_token st)
318  
    run_awaitable(A alloc, Task inner, std::stop_token st)
220  
        : resource_(std::move(alloc))
319  
        : resource_(std::move(alloc))
221  
        , st_(std::move(st))
320  
        , st_(std::move(st))
222  
        , inner_(std::move(inner))
321  
        , inner_(std::move(inner))
223  
    {
322  
    {
224  
    }
323  
    }
225  

324  

226  
    bool await_ready() const noexcept
325  
    bool await_ready() const noexcept
227  
    {
326  
    {
228  
        return inner_.await_ready();
327  
        return inner_.await_ready();
229  
    }
328  
    }
230  

329  

231  
    decltype(auto) await_resume()
330  
    decltype(auto) await_resume()
232  
    {
331  
    {
233  
        return inner_.await_resume();
332  
        return inner_.await_resume();
234  
    }
333  
    }
235  

334  

236 -
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const& caller_env)
335 +
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
237  
    {
336  
    {
238  
        auto h = inner_.handle();
337  
        auto h = inner_.handle();
239  
        auto& p = h.promise();
338  
        auto& p = h.promise();
240 -
        p.set_continuation(cont, caller_env.executor);
339 +
        p.set_continuation(cont);
241  

340  

242 -
        env_.executor = caller_env.executor;
341 +
        env_.executor = caller_env->executor;
243  
        if constexpr (InheritStopToken)
342  
        if constexpr (InheritStopToken)
244 -
            env_.stop_token = caller_env.stop_token;
343 +
            env_.stop_token = caller_env->stop_token;
245  
        else
344  
        else
246  
            env_.stop_token = st_;
345  
            env_.stop_token = st_;
247  

346  

248  
        if constexpr (!std::is_void_v<Alloc>)
347  
        if constexpr (!std::is_void_v<Alloc>)
249  
            env_.allocator = resource_.get();
348  
            env_.allocator = resource_.get();
250  
        else
349  
        else
251 -
            env_.allocator = caller_env.allocator;
350 +
            env_.allocator = caller_env->allocator;
252  

351  

253 -
        p.set_environment(env_);
352 +
        p.set_environment(&env_);
254  
        return h;
353  
        return h;
255  
    }
354  
    }
256  

355  

257  
    // Non-copyable
356  
    // Non-copyable
258  
    run_awaitable(run_awaitable const&) = delete;
357  
    run_awaitable(run_awaitable const&) = delete;
259  
    run_awaitable& operator=(run_awaitable const&) = delete;
358  
    run_awaitable& operator=(run_awaitable const&) = delete;
260  

359  

261  
    // Movable (no noexcept - Task may throw)
360  
    // Movable (no noexcept - Task may throw)
262  
    run_awaitable(run_awaitable&&) = default;
361  
    run_awaitable(run_awaitable&&) = default;
263  
    run_awaitable& operator=(run_awaitable&&) = default;
362  
    run_awaitable& operator=(run_awaitable&&) = default;
264  
};
363  
};
265  

364  

266  
//----------------------------------------------------------
365  
//----------------------------------------------------------
267  
//
366  
//
268  
// run_wrapper_ex - with executor
367  
// run_wrapper_ex - with executor
269  
//
368  
//
270  
//----------------------------------------------------------
369  
//----------------------------------------------------------
271  

370  

272  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
371  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
273  

372  

274  
    @tparam Ex The executor type.
373  
    @tparam Ex The executor type.
275  
    @tparam InheritStopToken If true, inherit caller's stop token.
374  
    @tparam InheritStopToken If true, inherit caller's stop token.
276  
    @tparam Alloc The allocator type (void for no allocator).
375  
    @tparam Alloc The allocator type (void for no allocator).
277  
*/
376  
*/
278  
template<Executor Ex, bool InheritStopToken, class Alloc>
377  
template<Executor Ex, bool InheritStopToken, class Alloc>
279  
class [[nodiscard]] run_wrapper_ex
378  
class [[nodiscard]] run_wrapper_ex
280  
{
379  
{
281  
    Ex ex_;
380  
    Ex ex_;
282  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
381  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
283  
    frame_memory_resource<Alloc> resource_;
382  
    frame_memory_resource<Alloc> resource_;
284  
    Alloc alloc_;  // Copy to pass to awaitable
383  
    Alloc alloc_;  // Copy to pass to awaitable
285  

384  

286  
public:
385  
public:
287  
    run_wrapper_ex(Ex ex, Alloc alloc)
386  
    run_wrapper_ex(Ex ex, Alloc alloc)
288  
        requires InheritStopToken
387  
        requires InheritStopToken
289  
        : ex_(std::move(ex))
388  
        : ex_(std::move(ex))
290  
        , resource_(alloc)
389  
        , resource_(alloc)
291  
        , alloc_(std::move(alloc))
390  
        , alloc_(std::move(alloc))
292  
    {
391  
    {
293  
        current_frame_allocator() = &resource_;
392  
        current_frame_allocator() = &resource_;
294  
    }
393  
    }
295  

394  

296  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
395  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
297  
        requires (!InheritStopToken)
396  
        requires (!InheritStopToken)
298  
        : ex_(std::move(ex))
397  
        : ex_(std::move(ex))
299  
        , st_(std::move(st))
398  
        , st_(std::move(st))
300  
        , resource_(alloc)
399  
        , resource_(alloc)
301  
        , alloc_(std::move(alloc))
400  
        , alloc_(std::move(alloc))
302  
    {
401  
    {
303  
        current_frame_allocator() = &resource_;
402  
        current_frame_allocator() = &resource_;
304  
    }
403  
    }
305  

404  

306  
    // Non-copyable, non-movable (must be used immediately)
405  
    // Non-copyable, non-movable (must be used immediately)
307  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
406  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
308  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
407  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
309  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
408  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
310  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
409  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
311  

410  

312 -
    template<IoLaunchableTask Task>
411 +
    template<IoRunnable Task>
313  
    [[nodiscard]] auto operator()(Task t) &&
412  
    [[nodiscard]] auto operator()(Task t) &&
314  
    {
413  
    {
315  
        if constexpr (InheritStopToken)
414  
        if constexpr (InheritStopToken)
316  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
415  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
317  
                std::move(ex_), std::move(alloc_), std::move(t)};
416  
                std::move(ex_), std::move(alloc_), std::move(t)};
318  
        else
417  
        else
319  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
418  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
320  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
419  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
321  
    }
420  
    }
322  
};
421  
};
323  

422  

324  
/// Specialization for memory_resource* - stores pointer directly.
423  
/// Specialization for memory_resource* - stores pointer directly.
325  
template<Executor Ex, bool InheritStopToken>
424  
template<Executor Ex, bool InheritStopToken>
326  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
425  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
327  
{
426  
{
328  
    Ex ex_;
427  
    Ex ex_;
329  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
428  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
330  
    std::pmr::memory_resource* mr_;
429  
    std::pmr::memory_resource* mr_;
331  

430  

332  
public:
431  
public:
333  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
432  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
334  
        requires InheritStopToken
433  
        requires InheritStopToken
335  
        : ex_(std::move(ex))
434  
        : ex_(std::move(ex))
336  
        , mr_(mr)
435  
        , mr_(mr)
337  
    {
436  
    {
338  
        current_frame_allocator() = mr_;
437  
        current_frame_allocator() = mr_;
339  
    }
438  
    }
340  

439  

341  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
440  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
342  
        requires (!InheritStopToken)
441  
        requires (!InheritStopToken)
343  
        : ex_(std::move(ex))
442  
        : ex_(std::move(ex))
344  
        , st_(std::move(st))
443  
        , st_(std::move(st))
345  
        , mr_(mr)
444  
        , mr_(mr)
346  
    {
445  
    {
347  
        current_frame_allocator() = mr_;
446  
        current_frame_allocator() = mr_;
348  
    }
447  
    }
349  

448  

350  
    // Non-copyable, non-movable (must be used immediately)
449  
    // Non-copyable, non-movable (must be used immediately)
351  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
450  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
352  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
451  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
353  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
452  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
354  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
453  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
355  

454  

356 -
    template<IoLaunchableTask Task>
455 +
    template<IoRunnable Task>
357  
    [[nodiscard]] auto operator()(Task t) &&
456  
    [[nodiscard]] auto operator()(Task t) &&
358  
    {
457  
    {
359  
        if constexpr (InheritStopToken)
458  
        if constexpr (InheritStopToken)
360  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
459  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
361  
                std::move(ex_), mr_, std::move(t)};
460  
                std::move(ex_), mr_, std::move(t)};
362  
        else
461  
        else
363  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
462  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
364  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
463  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
365  
    }
464  
    }
366  
};
465  
};
367  

466  

368  
/// Specialization for no allocator (void).
467  
/// Specialization for no allocator (void).
369  
template<Executor Ex, bool InheritStopToken>
468  
template<Executor Ex, bool InheritStopToken>
370  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
469  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
371  
{
470  
{
372  
    Ex ex_;
471  
    Ex ex_;
373  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
472  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
374  

473  

375  
public:
474  
public:
376  
    explicit run_wrapper_ex(Ex ex)
475  
    explicit run_wrapper_ex(Ex ex)
377  
        requires InheritStopToken
476  
        requires InheritStopToken
378  
        : ex_(std::move(ex))
477  
        : ex_(std::move(ex))
379  
    {
478  
    {
380  
    }
479  
    }
381  

480  

382  
    run_wrapper_ex(Ex ex, std::stop_token st)
481  
    run_wrapper_ex(Ex ex, std::stop_token st)
383  
        requires (!InheritStopToken)
482  
        requires (!InheritStopToken)
384  
        : ex_(std::move(ex))
483  
        : ex_(std::move(ex))
385  
        , st_(std::move(st))
484  
        , st_(std::move(st))
386  
    {
485  
    {
387  
    }
486  
    }
388  

487  

389  
    // Non-copyable, non-movable (must be used immediately)
488  
    // Non-copyable, non-movable (must be used immediately)
390  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
489  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
391  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
490  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
392  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
491  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
393  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
492  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
394  

493  

395 -
    template<IoLaunchableTask Task>
494 +
    template<IoRunnable Task>
396  
    [[nodiscard]] auto operator()(Task t) &&
495  
    [[nodiscard]] auto operator()(Task t) &&
397  
    {
496  
    {
398  
        if constexpr (InheritStopToken)
497  
        if constexpr (InheritStopToken)
399  
            return run_awaitable_ex<Task, Ex, true>{
498  
            return run_awaitable_ex<Task, Ex, true>{
400  
                std::move(ex_), std::move(t)};
499  
                std::move(ex_), std::move(t)};
401  
        else
500  
        else
402  
            return run_awaitable_ex<Task, Ex, false>{
501  
            return run_awaitable_ex<Task, Ex, false>{
403  
                std::move(ex_), std::move(t), std::move(st_)};
502  
                std::move(ex_), std::move(t), std::move(st_)};
404  
    }
503  
    }
405  
};
504  
};
406  

505  

407  
//----------------------------------------------------------
506  
//----------------------------------------------------------
408  
//
507  
//
409  
// run_wrapper - no executor (inherits caller's executor)
508  
// run_wrapper - no executor (inherits caller's executor)
410  
//
509  
//
411  
//----------------------------------------------------------
510  
//----------------------------------------------------------
412  

511  

413  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
512  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
414  

513  

415  
    @tparam InheritStopToken If true, inherit caller's stop token.
514  
    @tparam InheritStopToken If true, inherit caller's stop token.
416  
    @tparam Alloc The allocator type (void for no allocator).
515  
    @tparam Alloc The allocator type (void for no allocator).
417  
*/
516  
*/
418  
template<bool InheritStopToken, class Alloc>
517  
template<bool InheritStopToken, class Alloc>
419  
class [[nodiscard]] run_wrapper
518  
class [[nodiscard]] run_wrapper
420  
{
519  
{
421  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
520  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
422  
    frame_memory_resource<Alloc> resource_;
521  
    frame_memory_resource<Alloc> resource_;
423  
    Alloc alloc_;  // Copy to pass to awaitable
522  
    Alloc alloc_;  // Copy to pass to awaitable
424  

523  

425  
public:
524  
public:
426  
    explicit run_wrapper(Alloc alloc)
525  
    explicit run_wrapper(Alloc alloc)
427  
        requires InheritStopToken
526  
        requires InheritStopToken
428  
        : resource_(alloc)
527  
        : resource_(alloc)
429  
        , alloc_(std::move(alloc))
528  
        , alloc_(std::move(alloc))
430  
    {
529  
    {
431  
        current_frame_allocator() = &resource_;
530  
        current_frame_allocator() = &resource_;
432  
    }
531  
    }
433  

532  

434  
    run_wrapper(std::stop_token st, Alloc alloc)
533  
    run_wrapper(std::stop_token st, Alloc alloc)
435  
        requires (!InheritStopToken)
534  
        requires (!InheritStopToken)
436  
        : st_(std::move(st))
535  
        : st_(std::move(st))
437  
        , resource_(alloc)
536  
        , resource_(alloc)
438  
        , alloc_(std::move(alloc))
537  
        , alloc_(std::move(alloc))
439  
    {
538  
    {
440  
        current_frame_allocator() = &resource_;
539  
        current_frame_allocator() = &resource_;
441  
    }
540  
    }
442  

541  

443  
    // Non-copyable, non-movable (must be used immediately)
542  
    // Non-copyable, non-movable (must be used immediately)
444  
    run_wrapper(run_wrapper const&) = delete;
543  
    run_wrapper(run_wrapper const&) = delete;
445  
    run_wrapper(run_wrapper&&) = delete;
544  
    run_wrapper(run_wrapper&&) = delete;
446  
    run_wrapper& operator=(run_wrapper const&) = delete;
545  
    run_wrapper& operator=(run_wrapper const&) = delete;
447  
    run_wrapper& operator=(run_wrapper&&) = delete;
546  
    run_wrapper& operator=(run_wrapper&&) = delete;
448  

547  

449 -
    template<IoLaunchableTask Task>
548 +
    template<IoRunnable Task>
450  
    [[nodiscard]] auto operator()(Task t) &&
549  
    [[nodiscard]] auto operator()(Task t) &&
451  
    {
550  
    {
452  
        if constexpr (InheritStopToken)
551  
        if constexpr (InheritStopToken)
453  
            return run_awaitable<Task, true, Alloc>{
552  
            return run_awaitable<Task, true, Alloc>{
454  
                std::move(alloc_), std::move(t)};
553  
                std::move(alloc_), std::move(t)};
455  
        else
554  
        else
456  
            return run_awaitable<Task, false, Alloc>{
555  
            return run_awaitable<Task, false, Alloc>{
457  
                std::move(alloc_), std::move(t), std::move(st_)};
556  
                std::move(alloc_), std::move(t), std::move(st_)};
458  
    }
557  
    }
459  
};
558  
};
460  

559  

461  
/// Specialization for memory_resource* - stores pointer directly.
560  
/// Specialization for memory_resource* - stores pointer directly.
462  
template<bool InheritStopToken>
561  
template<bool InheritStopToken>
463  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
562  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
464  
{
563  
{
465  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
564  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
466  
    std::pmr::memory_resource* mr_;
565  
    std::pmr::memory_resource* mr_;
467  

566  

468  
public:
567  
public:
469  
    explicit run_wrapper(std::pmr::memory_resource* mr)
568  
    explicit run_wrapper(std::pmr::memory_resource* mr)
470  
        requires InheritStopToken
569  
        requires InheritStopToken
471  
        : mr_(mr)
570  
        : mr_(mr)
472  
    {
571  
    {
473  
        current_frame_allocator() = mr_;
572  
        current_frame_allocator() = mr_;
474  
    }
573  
    }
475  

574  

476  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
575  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
477  
        requires (!InheritStopToken)
576  
        requires (!InheritStopToken)
478  
        : st_(std::move(st))
577  
        : st_(std::move(st))
479  
        , mr_(mr)
578  
        , mr_(mr)
480  
    {
579  
    {
481  
        current_frame_allocator() = mr_;
580  
        current_frame_allocator() = mr_;
482  
    }
581  
    }
483  

582  

484  
    // Non-copyable, non-movable (must be used immediately)
583  
    // Non-copyable, non-movable (must be used immediately)
485  
    run_wrapper(run_wrapper const&) = delete;
584  
    run_wrapper(run_wrapper const&) = delete;
486  
    run_wrapper(run_wrapper&&) = delete;
585  
    run_wrapper(run_wrapper&&) = delete;
487  
    run_wrapper& operator=(run_wrapper const&) = delete;
586  
    run_wrapper& operator=(run_wrapper const&) = delete;
488  
    run_wrapper& operator=(run_wrapper&&) = delete;
587  
    run_wrapper& operator=(run_wrapper&&) = delete;
489  

588  

490 -
    template<IoLaunchableTask Task>
589 +
    template<IoRunnable Task>
491  
    [[nodiscard]] auto operator()(Task t) &&
590  
    [[nodiscard]] auto operator()(Task t) &&
492  
    {
591  
    {
493  
        if constexpr (InheritStopToken)
592  
        if constexpr (InheritStopToken)
494  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
593  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
495  
                mr_, std::move(t)};
594  
                mr_, std::move(t)};
496  
        else
595  
        else
497  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
596  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
498  
                mr_, std::move(t), std::move(st_)};
597  
                mr_, std::move(t), std::move(st_)};
499  
    }
598  
    }
500  
};
599  
};
501  

600  

502  
/// Specialization for stop_token only (no allocator).
601  
/// Specialization for stop_token only (no allocator).
503  
template<>
602  
template<>
504  
class [[nodiscard]] run_wrapper<false, void>
603  
class [[nodiscard]] run_wrapper<false, void>
505  
{
604  
{
506  
    std::stop_token st_;
605  
    std::stop_token st_;
507  

606  

508  
public:
607  
public:
509  
    explicit run_wrapper(std::stop_token st)
608  
    explicit run_wrapper(std::stop_token st)
510  
        : st_(std::move(st))
609  
        : st_(std::move(st))
511  
    {
610  
    {
512  
    }
611  
    }
513  

612  

514  
    // Non-copyable, non-movable (must be used immediately)
613  
    // Non-copyable, non-movable (must be used immediately)
515  
    run_wrapper(run_wrapper const&) = delete;
614  
    run_wrapper(run_wrapper const&) = delete;
516  
    run_wrapper(run_wrapper&&) = delete;
615  
    run_wrapper(run_wrapper&&) = delete;
517  
    run_wrapper& operator=(run_wrapper const&) = delete;
616  
    run_wrapper& operator=(run_wrapper const&) = delete;
518  
    run_wrapper& operator=(run_wrapper&&) = delete;
617  
    run_wrapper& operator=(run_wrapper&&) = delete;
519  

618  

520 -
    template<IoLaunchableTask Task>
619 +
    template<IoRunnable Task>
521  
    [[nodiscard]] auto operator()(Task t) &&
620  
    [[nodiscard]] auto operator()(Task t) &&
522  
    {
621  
    {
523  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
622  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
524  
    }
623  
    }
525  
};
624  
};
526  

625  

527  
} // namespace boost::capy::detail
626  
} // namespace boost::capy::detail
528  

627  

529  
namespace boost::capy {
628  
namespace boost::capy {
530  

629  

531  
//----------------------------------------------------------
630  
//----------------------------------------------------------
532  
//
631  
//
533  
// run() overloads - with executor
632  
// run() overloads - with executor
534  
//
633  
//
535  
//----------------------------------------------------------
634  
//----------------------------------------------------------
536  

635  

537  
/** Bind a task to execute on a specific executor.
636  
/** Bind a task to execute on a specific executor.
538  

637  

539  
    Returns a wrapper that accepts a task and produces an awaitable.
638  
    Returns a wrapper that accepts a task and produces an awaitable.
540  
    When co_awaited, the task runs on the specified executor.
639  
    When co_awaited, the task runs on the specified executor.
541  

640  

542  
    @par Example
641  
    @par Example
543  
    @code
642  
    @code
544  
    co_await run(other_executor)(my_task());
643  
    co_await run(other_executor)(my_task());
545  
    @endcode
644  
    @endcode
546  

645  

547  
    @param ex The executor on which the task should run.
646  
    @param ex The executor on which the task should run.
548  

647  

549  
    @return A wrapper that accepts a task for execution.
648  
    @return A wrapper that accepts a task for execution.
550  

649  

551  
    @see task
650  
    @see task
552  
    @see executor
651  
    @see executor
553  
*/
652  
*/
554  
template<Executor Ex>
653  
template<Executor Ex>
555  
[[nodiscard]] auto
654  
[[nodiscard]] auto
556  
run(Ex ex)
655  
run(Ex ex)
557  
{
656  
{
558  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
657  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
559  
}
658  
}
560  

659  

561  
/** Bind a task to an executor with a stop token.
660  
/** Bind a task to an executor with a stop token.
562  

661  

563  
    @param ex The executor on which the task should run.
662  
    @param ex The executor on which the task should run.
564  
    @param st The stop token for cooperative cancellation.
663  
    @param st The stop token for cooperative cancellation.
565  

664  

566  
    @return A wrapper that accepts a task for execution.
665  
    @return A wrapper that accepts a task for execution.
567  
*/
666  
*/
568  
template<Executor Ex>
667  
template<Executor Ex>
569  
[[nodiscard]] auto
668  
[[nodiscard]] auto
570  
run(Ex ex, std::stop_token st)
669  
run(Ex ex, std::stop_token st)
571  
{
670  
{
572  
    return detail::run_wrapper_ex<Ex, false, void>{
671  
    return detail::run_wrapper_ex<Ex, false, void>{
573  
        std::move(ex), std::move(st)};
672  
        std::move(ex), std::move(st)};
574  
}
673  
}
575  

674  

576  
/** Bind a task to an executor with a memory resource.
675  
/** Bind a task to an executor with a memory resource.
577  

676  

578  
    @param ex The executor on which the task should run.
677  
    @param ex The executor on which the task should run.
579  
    @param mr The memory resource for frame allocation.
678  
    @param mr The memory resource for frame allocation.
580  

679  

581  
    @return A wrapper that accepts a task for execution.
680  
    @return A wrapper that accepts a task for execution.
582  
*/
681  
*/
583  
template<Executor Ex>
682  
template<Executor Ex>
584  
[[nodiscard]] auto
683  
[[nodiscard]] auto
585  
run(Ex ex, std::pmr::memory_resource* mr)
684  
run(Ex ex, std::pmr::memory_resource* mr)
586  
{
685  
{
587  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
686  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
588  
        std::move(ex), mr};
687  
        std::move(ex), mr};
589  
}
688  
}
590  

689  

591  
/** Bind a task to an executor with a standard allocator.
690  
/** Bind a task to an executor with a standard allocator.
592  

691  

593  
    @param ex The executor on which the task should run.
692  
    @param ex The executor on which the task should run.
594  
    @param alloc The allocator for frame allocation.
693  
    @param alloc The allocator for frame allocation.
595  

694  

596  
    @return A wrapper that accepts a task for execution.
695  
    @return A wrapper that accepts a task for execution.
597  
*/
696  
*/
598  
template<Executor Ex, detail::Allocator Alloc>
697  
template<Executor Ex, detail::Allocator Alloc>
599  
[[nodiscard]] auto
698  
[[nodiscard]] auto
600  
run(Ex ex, Alloc alloc)
699  
run(Ex ex, Alloc alloc)
601  
{
700  
{
602  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
701  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
603  
        std::move(ex), std::move(alloc)};
702  
        std::move(ex), std::move(alloc)};
604  
}
703  
}
605  

704  

606  
/** Bind a task to an executor with stop token and memory resource.
705  
/** Bind a task to an executor with stop token and memory resource.
607  

706  

608  
    @param ex The executor on which the task should run.
707  
    @param ex The executor on which the task should run.
609  
    @param st The stop token for cooperative cancellation.
708  
    @param st The stop token for cooperative cancellation.
610  
    @param mr The memory resource for frame allocation.
709  
    @param mr The memory resource for frame allocation.
611  

710  

612  
    @return A wrapper that accepts a task for execution.
711  
    @return A wrapper that accepts a task for execution.
613  
*/
712  
*/
614  
template<Executor Ex>
713  
template<Executor Ex>
615  
[[nodiscard]] auto
714  
[[nodiscard]] auto
616  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
715  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
617  
{
716  
{
618  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
717  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
619  
        std::move(ex), std::move(st), mr};
718  
        std::move(ex), std::move(st), mr};
620  
}
719  
}
621  

720  

622  
/** Bind a task to an executor with stop token and standard allocator.
721  
/** Bind a task to an executor with stop token and standard allocator.
623  

722  

624  
    @param ex The executor on which the task should run.
723  
    @param ex The executor on which the task should run.
625  
    @param st The stop token for cooperative cancellation.
724  
    @param st The stop token for cooperative cancellation.
626  
    @param alloc The allocator for frame allocation.
725  
    @param alloc The allocator for frame allocation.
627  

726  

628  
    @return A wrapper that accepts a task for execution.
727  
    @return A wrapper that accepts a task for execution.
629  
*/
728  
*/
630  
template<Executor Ex, detail::Allocator Alloc>
729  
template<Executor Ex, detail::Allocator Alloc>
631  
[[nodiscard]] auto
730  
[[nodiscard]] auto
632  
run(Ex ex, std::stop_token st, Alloc alloc)
731  
run(Ex ex, std::stop_token st, Alloc alloc)
633  
{
732  
{
634  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
733  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
635  
        std::move(ex), std::move(st), std::move(alloc)};
734  
        std::move(ex), std::move(st), std::move(alloc)};
636  
}
735  
}
637  

736  

638  
//----------------------------------------------------------
737  
//----------------------------------------------------------
639  
//
738  
//
640  
// run() overloads - no executor (inherits caller's)
739  
// run() overloads - no executor (inherits caller's)
641  
//
740  
//
642  
//----------------------------------------------------------
741  
//----------------------------------------------------------
643  

742  

644  
/** Run a task with a custom stop token.
743  
/** Run a task with a custom stop token.
645  

744  

646  
    The task inherits the caller's executor. Only the stop token
745  
    The task inherits the caller's executor. Only the stop token
647  
    is overridden.
746  
    is overridden.
648  

747  

649  
    @par Example
748  
    @par Example
650  
    @code
749  
    @code
651  
    std::stop_source source;
750  
    std::stop_source source;
652  
    co_await run(source.get_token())(cancellable_task());
751  
    co_await run(source.get_token())(cancellable_task());
653  
    @endcode
752  
    @endcode
654  

753  

655  
    @param st The stop token for cooperative cancellation.
754  
    @param st The stop token for cooperative cancellation.
656  

755  

657  
    @return A wrapper that accepts a task for execution.
756  
    @return A wrapper that accepts a task for execution.
658  
*/
757  
*/
659  
[[nodiscard]] inline auto
758  
[[nodiscard]] inline auto
660  
run(std::stop_token st)
759  
run(std::stop_token st)
661  
{
760  
{
662  
    return detail::run_wrapper<false, void>{std::move(st)};
761  
    return detail::run_wrapper<false, void>{std::move(st)};
663  
}
762  
}
664  

763  

665  
/** Run a task with a custom memory resource.
764  
/** Run a task with a custom memory resource.
666  

765  

667  
    The task inherits the caller's executor. The memory resource
766  
    The task inherits the caller's executor. The memory resource
668  
    is used for nested frame allocations.
767  
    is used for nested frame allocations.
669  

768  

670  
    @param mr The memory resource for frame allocation.
769  
    @param mr The memory resource for frame allocation.
671  

770  

672  
    @return A wrapper that accepts a task for execution.
771  
    @return A wrapper that accepts a task for execution.
673  
*/
772  
*/
674  
[[nodiscard]] inline auto
773  
[[nodiscard]] inline auto
675  
run(std::pmr::memory_resource* mr)
774  
run(std::pmr::memory_resource* mr)
676  
{
775  
{
677  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
776  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
678  
}
777  
}
679  

778  

680  
/** Run a task with a custom standard allocator.
779  
/** Run a task with a custom standard allocator.
681  

780  

682  
    The task inherits the caller's executor. The allocator is used
781  
    The task inherits the caller's executor. The allocator is used
683  
    for nested frame allocations.
782  
    for nested frame allocations.
684  

783  

685  
    @param alloc The allocator for frame allocation.
784  
    @param alloc The allocator for frame allocation.
686  

785  

687  
    @return A wrapper that accepts a task for execution.
786  
    @return A wrapper that accepts a task for execution.
688  
*/
787  
*/
689  
template<detail::Allocator Alloc>
788  
template<detail::Allocator Alloc>
690  
[[nodiscard]] auto
789  
[[nodiscard]] auto
691  
run(Alloc alloc)
790  
run(Alloc alloc)
692  
{
791  
{
693  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
792  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
694  
}
793  
}
695  

794  

696  
/** Run a task with stop token and memory resource.
795  
/** Run a task with stop token and memory resource.
697  

796  

698  
    The task inherits the caller's executor.
797  
    The task inherits the caller's executor.
699  

798  

700  
    @param st The stop token for cooperative cancellation.
799  
    @param st The stop token for cooperative cancellation.
701  
    @param mr The memory resource for frame allocation.
800  
    @param mr The memory resource for frame allocation.
702  

801  

703  
    @return A wrapper that accepts a task for execution.
802  
    @return A wrapper that accepts a task for execution.
704  
*/
803  
*/
705  
[[nodiscard]] inline auto
804  
[[nodiscard]] inline auto
706  
run(std::stop_token st, std::pmr::memory_resource* mr)
805  
run(std::stop_token st, std::pmr::memory_resource* mr)
707  
{
806  
{
708  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
807  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
709  
        std::move(st), mr};
808  
        std::move(st), mr};
710  
}
809  
}
711  

810  

712  
/** Run a task with stop token and standard allocator.
811  
/** Run a task with stop token and standard allocator.
713  

812  

714  
    The task inherits the caller's executor.
813  
    The task inherits the caller's executor.
715  

814  

716  
    @param st The stop token for cooperative cancellation.
815  
    @param st The stop token for cooperative cancellation.
717  
    @param alloc The allocator for frame allocation.
816  
    @param alloc The allocator for frame allocation.
718  

817  

719  
    @return A wrapper that accepts a task for execution.
818  
    @return A wrapper that accepts a task for execution.
720  
*/
819  
*/
721  
template<detail::Allocator Alloc>
820  
template<detail::Allocator Alloc>
722  
[[nodiscard]] auto
821  
[[nodiscard]] auto
723  
run(std::stop_token st, Alloc alloc)
822  
run(std::stop_token st, Alloc alloc)
724  
{
823  
{
725  
    return detail::run_wrapper<false, Alloc>{
824  
    return detail::run_wrapper<false, Alloc>{
726  
        std::move(st), std::move(alloc)};
825  
        std::move(st), std::move(alloc)};
727  
}
826  
}
728  

827  

729  
} // namespace boost::capy
828  
} // namespace boost::capy
730  

829  

731  
#endif
830  
#endif