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

22  

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

28  

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

32  

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

34  

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

36  

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

44  

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

48  

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

53  

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

59  

60  
namespace boost::capy::detail {
60  
namespace boost::capy::detail {
61  

61  

62  
/** Minimal coroutine that dispatches through the caller's executor.
62  
/** Minimal coroutine that dispatches through the caller's executor.
63  

63  

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

69  

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

78  

79  
        dispatch_trampoline get_return_object() noexcept
79  
        dispatch_trampoline get_return_object() noexcept
80  
        {
80  
        {
81  
            return dispatch_trampoline{
81  
            return dispatch_trampoline{
82  
                std::coroutine_handle<promise_type>::from_promise(*this)};
82  
                std::coroutine_handle<promise_type>::from_promise(*this)};
83  
        }
83  
        }
84  

84  

85  
        std::suspend_always initial_suspend() noexcept { return {}; }
85  
        std::suspend_always initial_suspend() noexcept { return {}; }
86  

86  

87  
        auto final_suspend() noexcept
87  
        auto final_suspend() noexcept
88  
        {
88  
        {
89  
            struct awaiter
89  
            struct awaiter
90  
            {
90  
            {
91  
                promise_type* p_;
91  
                promise_type* p_;
92  
                bool await_ready() const noexcept { return false; }
92  
                bool await_ready() const noexcept { return false; }
93  

93  

94  
                auto await_suspend(
94  
                auto await_suspend(
95  
                    std::coroutine_handle<>) noexcept
95  
                    std::coroutine_handle<>) noexcept
96  
                {
96  
                {
97  
                    return detail::symmetric_transfer(
97  
                    return detail::symmetric_transfer(
98  
                        p_->caller_ex_.dispatch(p_->parent_));
98  
                        p_->caller_ex_.dispatch(p_->parent_));
99  
                }
99  
                }
100  

100  

101  
                void await_resume() const noexcept {}
101  
                void await_resume() const noexcept {}
102  
            };
102  
            };
103  
            return awaiter{this};
103  
            return awaiter{this};
104  
        }
104  
        }
105  

105  

106  
        void return_void() noexcept {}
106  
        void return_void() noexcept {}
107  
        void unhandled_exception() noexcept {}
107  
        void unhandled_exception() noexcept {}
108  
    };
108  
    };
109  

109  

110  
    std::coroutine_handle<promise_type> h_{nullptr};
110  
    std::coroutine_handle<promise_type> h_{nullptr};
111  

111  

112  
    dispatch_trampoline() noexcept = default;
112  
    dispatch_trampoline() noexcept = default;
113  

113  

114  
    ~dispatch_trampoline()
114  
    ~dispatch_trampoline()
115  
    {
115  
    {
116  
        if(h_) h_.destroy();
116  
        if(h_) h_.destroy();
117  
    }
117  
    }
118  

118  

119  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
119  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
120  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
120  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
121  

121  

122  
    dispatch_trampoline(dispatch_trampoline&& o) noexcept
122  
    dispatch_trampoline(dispatch_trampoline&& o) noexcept
123  
        : h_(std::exchange(o.h_, nullptr)) {}
123  
        : h_(std::exchange(o.h_, nullptr)) {}
124  

124  

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

134  

135  
private:
135  
private:
136  
    explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
136  
    explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
137  
        : h_(h) {}
137  
        : h_(h) {}
138  
};
138  
};
139  

139  

140  
inline dispatch_trampoline make_dispatch_trampoline()
140  
inline dispatch_trampoline make_dispatch_trampoline()
141  
{
141  
{
142  
    co_return;
142  
    co_return;
143  
}
143  
}
144  

144  

145  
/** Awaitable that binds an IoRunnable to a specific executor.
145  
/** Awaitable that binds an IoRunnable to a specific executor.
146  

146  

147  
    Stores the executor and inner task by value. When co_awaited, the
147  
    Stores the executor and inner task by value. When co_awaited, the
148  
    co_await expression's lifetime extension keeps both alive for the
148  
    co_await expression's lifetime extension keeps both alive for the
149  
    duration of the operation.
149  
    duration of the operation.
150  

150  

151  
    A dispatch trampoline handles the executor switch on completion:
151  
    A dispatch trampoline handles the executor switch on completion:
152  
    the inner task's `final_suspend` resumes the trampoline, which
152  
    the inner task's `final_suspend` resumes the trampoline, which
153  
    dispatches back through the caller's executor.
153  
    dispatches back through the caller's executor.
154  

154  

155  
    The `io_env` is owned by this awaitable and is guaranteed to
155  
    The `io_env` is owned by this awaitable and is guaranteed to
156  
    outlive the inner task and all awaitables in its chain. Awaitables
156  
    outlive the inner task and all awaitables in its chain. Awaitables
157  
    may store `io_env const*` without concern for dangling references.
157  
    may store `io_env const*` without concern for dangling references.
158  

158  

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

173  

174  
    // void allocator, inherit stop token
174  
    // void allocator, inherit stop token
175  
    run_awaitable_ex(Ex ex, Task inner)
175  
    run_awaitable_ex(Ex ex, Task inner)
176  
        requires (InheritStopToken && std::is_void_v<Alloc>)
176  
        requires (InheritStopToken && std::is_void_v<Alloc>)
177  
        : ex_(std::move(ex))
177  
        : ex_(std::move(ex))
178  
        , inner_(std::move(inner))
178  
        , inner_(std::move(inner))
179  
    {
179  
    {
180  
    }
180  
    }
181  

181  

182  
    // void allocator, explicit stop token
182  
    // void allocator, explicit stop token
183  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
183  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
184  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
184  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
185  
        : ex_(std::move(ex))
185  
        : ex_(std::move(ex))
186  
        , st_(std::move(st))
186  
        , st_(std::move(st))
187  
        , inner_(std::move(inner))
187  
        , inner_(std::move(inner))
188  
    {
188  
    {
189  
    }
189  
    }
190  

190  

191  
    // with allocator, inherit stop token (use template to avoid void parameter)
191  
    // with allocator, inherit stop token (use template to avoid void parameter)
192  
    template<class A>
192  
    template<class A>
193  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
193  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
194  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
194  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
195  
        : ex_(std::move(ex))
195  
        : ex_(std::move(ex))
196  
        , resource_(std::move(alloc))
196  
        , resource_(std::move(alloc))
197  
        , inner_(std::move(inner))
197  
        , inner_(std::move(inner))
198  
    {
198  
    {
199  
    }
199  
    }
200  

200  

201  
    // with allocator, explicit stop token (use template to avoid void parameter)
201  
    // with allocator, explicit stop token (use template to avoid void parameter)
202  
    template<class A>
202  
    template<class A>
203  
        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>)
204  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
204  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
205  
        : ex_(std::move(ex))
205  
        : ex_(std::move(ex))
206  
        , resource_(std::move(alloc))
206  
        , resource_(std::move(alloc))
207  
        , st_(std::move(st))
207  
        , st_(std::move(st))
208  
        , inner_(std::move(inner))
208  
        , inner_(std::move(inner))
209  
    {
209  
    {
210  
    }
210  
    }
211  

211  

212  
    bool await_ready() const noexcept
212  
    bool await_ready() const noexcept
213  
    {
213  
    {
214  
        return inner_.await_ready();
214  
        return inner_.await_ready();
215  
    }
215  
    }
216  

216  

217  
    decltype(auto) await_resume()
217  
    decltype(auto) await_resume()
218  
    {
218  
    {
219  
        return inner_.await_resume();
219  
        return inner_.await_resume();
220  
    }
220  
    }
221  

221  

222  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
222  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
223  
    {
223  
    {
224  
        tr_ = make_dispatch_trampoline();
224  
        tr_ = make_dispatch_trampoline();
225  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
225  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
226  
        tr_.h_.promise().parent_ = cont;
226  
        tr_.h_.promise().parent_ = cont;
227  

227  

228  
        auto h = inner_.handle();
228  
        auto h = inner_.handle();
229  
        auto& p = h.promise();
229  
        auto& p = h.promise();
230  
        p.set_continuation(tr_.h_);
230  
        p.set_continuation(tr_.h_);
231  

231  

232  
        env_.executor = ex_;
232  
        env_.executor = ex_;
233  
        if constexpr (InheritStopToken)
233  
        if constexpr (InheritStopToken)
234  
            env_.stop_token = caller_env->stop_token;
234  
            env_.stop_token = caller_env->stop_token;
235  
        else
235  
        else
236  
            env_.stop_token = st_;
236  
            env_.stop_token = st_;
237  

237  

238  
        if constexpr (!std::is_void_v<Alloc>)
238  
        if constexpr (!std::is_void_v<Alloc>)
239  
            env_.frame_allocator = resource_.get();
239  
            env_.frame_allocator = resource_.get();
240  
        else
240  
        else
241  
            env_.frame_allocator = caller_env->frame_allocator;
241  
            env_.frame_allocator = caller_env->frame_allocator;
242  

242  

243  
        p.set_environment(&env_);
243  
        p.set_environment(&env_);
244  
        return h;
244  
        return h;
245  
    }
245  
    }
246  

246  

247  
    // Non-copyable
247  
    // Non-copyable
248  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
248  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
249  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
249  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
250  

250  

251  
    // Movable (no noexcept - Task may throw)
251  
    // Movable (no noexcept - Task may throw)
252  
    run_awaitable_ex(run_awaitable_ex&&) = default;
252  
    run_awaitable_ex(run_awaitable_ex&&) = default;
253  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
253  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
254  
};
254  
};
255  

255  

256  
/** Awaitable that runs a task with optional stop_token override.
256  
/** Awaitable that runs a task with optional stop_token override.
257  

257  

258  
    Does NOT store an executor - the task inherits the caller's executor
258  
    Does NOT store an executor - the task inherits the caller's executor
259  
    directly. Executors always match, so no dispatch trampoline is needed.
259  
    directly. Executors always match, so no dispatch trampoline is needed.
260  
    The inner task's `final_suspend` resumes the parent directly via
260  
    The inner task's `final_suspend` resumes the parent directly via
261  
    unconditional symmetric transfer.
261  
    unconditional symmetric transfer.
262  

262  

263  
    @tparam Task The IoRunnable type
263  
    @tparam Task The IoRunnable type
264  
    @tparam InheritStopToken If true, inherit caller's stop token
264  
    @tparam InheritStopToken If true, inherit caller's stop token
265  
    @tparam Alloc The allocator type (void for no allocator)
265  
    @tparam Alloc The allocator type (void for no allocator)
266  
*/
266  
*/
267  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
267  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
268  
struct [[nodiscard]] run_awaitable
268  
struct [[nodiscard]] run_awaitable
269  
{
269  
{
270  
    frame_memory_resource<Alloc> resource_;
270  
    frame_memory_resource<Alloc> resource_;
271  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
271  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
272  
    io_env env_;
272  
    io_env env_;
273  
    Task inner_;  // Last: destroyed first, while env_ is still valid
273  
    Task inner_;  // Last: destroyed first, while env_ is still valid
274  

274  

275  
    // void allocator, inherit stop token
275  
    // void allocator, inherit stop token
276  
    explicit run_awaitable(Task inner)
276  
    explicit run_awaitable(Task inner)
277  
        requires (InheritStopToken && std::is_void_v<Alloc>)
277  
        requires (InheritStopToken && std::is_void_v<Alloc>)
278  
        : inner_(std::move(inner))
278  
        : inner_(std::move(inner))
279  
    {
279  
    {
280  
    }
280  
    }
281  

281  

282  
    // void allocator, explicit stop token
282  
    // void allocator, explicit stop token
283  
    run_awaitable(Task inner, std::stop_token st)
283  
    run_awaitable(Task inner, std::stop_token st)
284  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
284  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
285  
        : st_(std::move(st))
285  
        : st_(std::move(st))
286  
        , inner_(std::move(inner))
286  
        , inner_(std::move(inner))
287  
    {
287  
    {
288  
    }
288  
    }
289  

289  

290  
    // with allocator, inherit stop token (use template to avoid void parameter)
290  
    // with allocator, inherit stop token (use template to avoid void parameter)
291  
    template<class A>
291  
    template<class A>
292  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
292  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
293  
    run_awaitable(A alloc, Task inner)
293  
    run_awaitable(A alloc, Task inner)
294  
        : resource_(std::move(alloc))
294  
        : resource_(std::move(alloc))
295  
        , inner_(std::move(inner))
295  
        , inner_(std::move(inner))
296  
    {
296  
    {
297  
    }
297  
    }
298  

298  

299  
    // with allocator, explicit stop token (use template to avoid void parameter)
299  
    // with allocator, explicit stop token (use template to avoid void parameter)
300  
    template<class A>
300  
    template<class A>
301  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
301  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
302  
    run_awaitable(A alloc, Task inner, std::stop_token st)
302  
    run_awaitable(A alloc, Task inner, std::stop_token st)
303  
        : resource_(std::move(alloc))
303  
        : resource_(std::move(alloc))
304  
        , st_(std::move(st))
304  
        , st_(std::move(st))
305  
        , inner_(std::move(inner))
305  
        , inner_(std::move(inner))
306  
    {
306  
    {
307  
    }
307  
    }
308  

308  

309  
    bool await_ready() const noexcept
309  
    bool await_ready() const noexcept
310  
    {
310  
    {
311  
        return inner_.await_ready();
311  
        return inner_.await_ready();
312  
    }
312  
    }
313  

313  

314  
    decltype(auto) await_resume()
314  
    decltype(auto) await_resume()
315  
    {
315  
    {
316  
        return inner_.await_resume();
316  
        return inner_.await_resume();
317  
    }
317  
    }
318  

318  

319  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
319  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
320  
    {
320  
    {
321  
        auto h = inner_.handle();
321  
        auto h = inner_.handle();
322  
        auto& p = h.promise();
322  
        auto& p = h.promise();
323  
        p.set_continuation(cont);
323  
        p.set_continuation(cont);
324  

324  

325  
        env_.executor = caller_env->executor;
325  
        env_.executor = caller_env->executor;
326  
        if constexpr (InheritStopToken)
326  
        if constexpr (InheritStopToken)
327  
            env_.stop_token = caller_env->stop_token;
327  
            env_.stop_token = caller_env->stop_token;
328  
        else
328  
        else
329  
            env_.stop_token = st_;
329  
            env_.stop_token = st_;
330  

330  

331  
        if constexpr (!std::is_void_v<Alloc>)
331  
        if constexpr (!std::is_void_v<Alloc>)
332  
            env_.frame_allocator = resource_.get();
332  
            env_.frame_allocator = resource_.get();
333  
        else
333  
        else
334  
            env_.frame_allocator = caller_env->frame_allocator;
334  
            env_.frame_allocator = caller_env->frame_allocator;
335  

335  

336  
        p.set_environment(&env_);
336  
        p.set_environment(&env_);
337  
        return h;
337  
        return h;
338  
    }
338  
    }
339  

339  

340  
    // Non-copyable
340  
    // Non-copyable
341  
    run_awaitable(run_awaitable const&) = delete;
341  
    run_awaitable(run_awaitable const&) = delete;
342  
    run_awaitable& operator=(run_awaitable const&) = delete;
342  
    run_awaitable& operator=(run_awaitable const&) = delete;
343  

343  

344  
    // Movable (no noexcept - Task may throw)
344  
    // Movable (no noexcept - Task may throw)
345  
    run_awaitable(run_awaitable&&) = default;
345  
    run_awaitable(run_awaitable&&) = default;
346  
    run_awaitable& operator=(run_awaitable&&) = default;
346  
    run_awaitable& operator=(run_awaitable&&) = default;
347  
};
347  
};
348  

348  

349  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
349  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
350  

350  

351  
    @tparam Ex The executor type.
351  
    @tparam Ex The executor type.
352  
    @tparam InheritStopToken If true, inherit caller's stop token.
352  
    @tparam InheritStopToken If true, inherit caller's stop token.
353  
    @tparam Alloc The allocator type (void for no allocator).
353  
    @tparam Alloc The allocator type (void for no allocator).
354  
*/
354  
*/
355  
template<Executor Ex, bool InheritStopToken, class Alloc>
355  
template<Executor Ex, bool InheritStopToken, class Alloc>
356  
class [[nodiscard]] run_wrapper_ex
356  
class [[nodiscard]] run_wrapper_ex
357  
{
357  
{
358  
    Ex ex_;
358  
    Ex ex_;
359  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
359  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
360  
    frame_memory_resource<Alloc> resource_;
360  
    frame_memory_resource<Alloc> resource_;
361  
    Alloc alloc_;  // Copy to pass to awaitable
361  
    Alloc alloc_;  // Copy to pass to awaitable
362  

362  

363  
public:
363  
public:
364  
    run_wrapper_ex(Ex ex, Alloc alloc)
364  
    run_wrapper_ex(Ex ex, Alloc alloc)
365  
        requires InheritStopToken
365  
        requires InheritStopToken
366  
        : ex_(std::move(ex))
366  
        : ex_(std::move(ex))
367  
        , resource_(alloc)
367  
        , resource_(alloc)
368  
        , alloc_(std::move(alloc))
368  
        , alloc_(std::move(alloc))
369  
    {
369  
    {
370  
        set_current_frame_allocator(&resource_);
370  
        set_current_frame_allocator(&resource_);
371  
    }
371  
    }
372  

372  

373  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
373  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
374  
        requires (!InheritStopToken)
374  
        requires (!InheritStopToken)
375  
        : ex_(std::move(ex))
375  
        : ex_(std::move(ex))
376  
        , st_(std::move(st))
376  
        , st_(std::move(st))
377  
        , resource_(alloc)
377  
        , resource_(alloc)
378  
        , alloc_(std::move(alloc))
378  
        , alloc_(std::move(alloc))
379  
    {
379  
    {
380  
        set_current_frame_allocator(&resource_);
380  
        set_current_frame_allocator(&resource_);
381  
    }
381  
    }
382  

382  

383  
    // Non-copyable, non-movable (must be used immediately)
383  
    // Non-copyable, non-movable (must be used immediately)
384  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
384  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
385  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
385  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
386  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
386  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
387  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
387  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
388  

388  

389  
    template<IoRunnable Task>
389  
    template<IoRunnable Task>
390  
    [[nodiscard]] auto operator()(Task t) &&
390  
    [[nodiscard]] auto operator()(Task t) &&
391  
    {
391  
    {
392  
        if constexpr (InheritStopToken)
392  
        if constexpr (InheritStopToken)
393  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
393  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
394  
                std::move(ex_), std::move(alloc_), std::move(t)};
394  
                std::move(ex_), std::move(alloc_), std::move(t)};
395  
        else
395  
        else
396  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
396  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
397  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
397  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
398  
    }
398  
    }
399  
};
399  
};
400  

400  

401  
/// Specialization for memory_resource* - stores pointer directly.
401  
/// Specialization for memory_resource* - stores pointer directly.
402  
template<Executor Ex, bool InheritStopToken>
402  
template<Executor Ex, bool InheritStopToken>
403  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
403  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
404  
{
404  
{
405  
    Ex ex_;
405  
    Ex ex_;
406  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
406  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
407  
    std::pmr::memory_resource* mr_;
407  
    std::pmr::memory_resource* mr_;
408  

408  

409  
public:
409  
public:
410  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
410  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
411  
        requires InheritStopToken
411  
        requires InheritStopToken
412  
        : ex_(std::move(ex))
412  
        : ex_(std::move(ex))
413  
        , mr_(mr)
413  
        , mr_(mr)
414  
    {
414  
    {
415  
        set_current_frame_allocator(mr_);
415  
        set_current_frame_allocator(mr_);
416  
    }
416  
    }
417  

417  

418  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
418  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
419  
        requires (!InheritStopToken)
419  
        requires (!InheritStopToken)
420  
        : ex_(std::move(ex))
420  
        : ex_(std::move(ex))
421  
        , st_(std::move(st))
421  
        , st_(std::move(st))
422  
        , mr_(mr)
422  
        , mr_(mr)
423  
    {
423  
    {
424  
        set_current_frame_allocator(mr_);
424  
        set_current_frame_allocator(mr_);
425  
    }
425  
    }
426  

426  

427  
    // Non-copyable, non-movable (must be used immediately)
427  
    // Non-copyable, non-movable (must be used immediately)
428  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
428  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
429  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
429  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
430  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
430  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
431  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
431  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
432  

432  

433  
    template<IoRunnable Task>
433  
    template<IoRunnable Task>
434  
    [[nodiscard]] auto operator()(Task t) &&
434  
    [[nodiscard]] auto operator()(Task t) &&
435  
    {
435  
    {
436  
        if constexpr (InheritStopToken)
436  
        if constexpr (InheritStopToken)
437  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
437  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
438  
                std::move(ex_), mr_, std::move(t)};
438  
                std::move(ex_), mr_, std::move(t)};
439  
        else
439  
        else
440  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
440  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
441  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
441  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
442  
    }
442  
    }
443  
};
443  
};
444  

444  

445  
/// Specialization for no allocator (void).
445  
/// Specialization for no allocator (void).
446  
template<Executor Ex, bool InheritStopToken>
446  
template<Executor Ex, bool InheritStopToken>
447  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
447  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
448  
{
448  
{
449  
    Ex ex_;
449  
    Ex ex_;
450  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
450  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
451  

451  

452  
public:
452  
public:
453  
    explicit run_wrapper_ex(Ex ex)
453  
    explicit run_wrapper_ex(Ex ex)
454  
        requires InheritStopToken
454  
        requires InheritStopToken
455  
        : ex_(std::move(ex))
455  
        : ex_(std::move(ex))
456  
    {
456  
    {
457  
    }
457  
    }
458  

458  

459  
    run_wrapper_ex(Ex ex, std::stop_token st)
459  
    run_wrapper_ex(Ex ex, std::stop_token st)
460  
        requires (!InheritStopToken)
460  
        requires (!InheritStopToken)
461  
        : ex_(std::move(ex))
461  
        : ex_(std::move(ex))
462  
        , st_(std::move(st))
462  
        , st_(std::move(st))
463  
    {
463  
    {
464  
    }
464  
    }
465  

465  

466  
    // Non-copyable, non-movable (must be used immediately)
466  
    // Non-copyable, non-movable (must be used immediately)
467  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
467  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
468  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
468  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
469  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
469  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
470  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
470  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
471  

471  

472  
    template<IoRunnable Task>
472  
    template<IoRunnable Task>
473  
    [[nodiscard]] auto operator()(Task t) &&
473  
    [[nodiscard]] auto operator()(Task t) &&
474  
    {
474  
    {
475  
        if constexpr (InheritStopToken)
475  
        if constexpr (InheritStopToken)
476  
            return run_awaitable_ex<Task, Ex, true>{
476  
            return run_awaitable_ex<Task, Ex, true>{
477  
                std::move(ex_), std::move(t)};
477  
                std::move(ex_), std::move(t)};
478  
        else
478  
        else
479  
            return run_awaitable_ex<Task, Ex, false>{
479  
            return run_awaitable_ex<Task, Ex, false>{
480  
                std::move(ex_), std::move(t), std::move(st_)};
480  
                std::move(ex_), std::move(t), std::move(st_)};
481  
    }
481  
    }
482  
};
482  
};
483  

483  

484  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
484  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
485  

485  

486  
    @tparam InheritStopToken If true, inherit caller's stop token.
486  
    @tparam InheritStopToken If true, inherit caller's stop token.
487  
    @tparam Alloc The allocator type (void for no allocator).
487  
    @tparam Alloc The allocator type (void for no allocator).
488  
*/
488  
*/
489  
template<bool InheritStopToken, class Alloc>
489  
template<bool InheritStopToken, class Alloc>
490  
class [[nodiscard]] run_wrapper
490  
class [[nodiscard]] run_wrapper
491  
{
491  
{
492  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
492  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
493  
    frame_memory_resource<Alloc> resource_;
493  
    frame_memory_resource<Alloc> resource_;
494  
    Alloc alloc_;  // Copy to pass to awaitable
494  
    Alloc alloc_;  // Copy to pass to awaitable
495  

495  

496  
public:
496  
public:
497  
    explicit run_wrapper(Alloc alloc)
497  
    explicit run_wrapper(Alloc alloc)
498  
        requires InheritStopToken
498  
        requires InheritStopToken
499  
        : resource_(alloc)
499  
        : resource_(alloc)
500  
        , alloc_(std::move(alloc))
500  
        , alloc_(std::move(alloc))
501  
    {
501  
    {
502  
        set_current_frame_allocator(&resource_);
502  
        set_current_frame_allocator(&resource_);
503  
    }
503  
    }
504  

504  

505  
    run_wrapper(std::stop_token st, Alloc alloc)
505  
    run_wrapper(std::stop_token st, Alloc alloc)
506  
        requires (!InheritStopToken)
506  
        requires (!InheritStopToken)
507  
        : st_(std::move(st))
507  
        : st_(std::move(st))
508  
        , resource_(alloc)
508  
        , resource_(alloc)
509  
        , alloc_(std::move(alloc))
509  
        , alloc_(std::move(alloc))
510  
    {
510  
    {
511  
        set_current_frame_allocator(&resource_);
511  
        set_current_frame_allocator(&resource_);
512  
    }
512  
    }
513  

513  

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

519  

520  
    template<IoRunnable Task>
520  
    template<IoRunnable Task>
521  
    [[nodiscard]] auto operator()(Task t) &&
521  
    [[nodiscard]] auto operator()(Task t) &&
522  
    {
522  
    {
523  
        if constexpr (InheritStopToken)
523  
        if constexpr (InheritStopToken)
524  
            return run_awaitable<Task, true, Alloc>{
524  
            return run_awaitable<Task, true, Alloc>{
525  
                std::move(alloc_), std::move(t)};
525  
                std::move(alloc_), std::move(t)};
526  
        else
526  
        else
527  
            return run_awaitable<Task, false, Alloc>{
527  
            return run_awaitable<Task, false, Alloc>{
528  
                std::move(alloc_), std::move(t), std::move(st_)};
528  
                std::move(alloc_), std::move(t), std::move(st_)};
529  
    }
529  
    }
530  
};
530  
};
531  

531  

532  
/// Specialization for memory_resource* - stores pointer directly.
532  
/// Specialization for memory_resource* - stores pointer directly.
533  
template<bool InheritStopToken>
533  
template<bool InheritStopToken>
534  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
534  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
535  
{
535  
{
536  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
536  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
537  
    std::pmr::memory_resource* mr_;
537  
    std::pmr::memory_resource* mr_;
538  

538  

539  
public:
539  
public:
540  
    explicit run_wrapper(std::pmr::memory_resource* mr)
540  
    explicit run_wrapper(std::pmr::memory_resource* mr)
541  
        requires InheritStopToken
541  
        requires InheritStopToken
542  
        : mr_(mr)
542  
        : mr_(mr)
543  
    {
543  
    {
544  
        set_current_frame_allocator(mr_);
544  
        set_current_frame_allocator(mr_);
545  
    }
545  
    }
546  

546  

547  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
547  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
548  
        requires (!InheritStopToken)
548  
        requires (!InheritStopToken)
549  
        : st_(std::move(st))
549  
        : st_(std::move(st))
550  
        , mr_(mr)
550  
        , mr_(mr)
551  
    {
551  
    {
552  
        set_current_frame_allocator(mr_);
552  
        set_current_frame_allocator(mr_);
553  
    }
553  
    }
554  

554  

555  
    // Non-copyable, non-movable (must be used immediately)
555  
    // Non-copyable, non-movable (must be used immediately)
556  
    run_wrapper(run_wrapper const&) = delete;
556  
    run_wrapper(run_wrapper const&) = delete;
557  
    run_wrapper(run_wrapper&&) = delete;
557  
    run_wrapper(run_wrapper&&) = delete;
558  
    run_wrapper& operator=(run_wrapper const&) = delete;
558  
    run_wrapper& operator=(run_wrapper const&) = delete;
559  
    run_wrapper& operator=(run_wrapper&&) = delete;
559  
    run_wrapper& operator=(run_wrapper&&) = delete;
560  

560  

561  
    template<IoRunnable Task>
561  
    template<IoRunnable Task>
562  
    [[nodiscard]] auto operator()(Task t) &&
562  
    [[nodiscard]] auto operator()(Task t) &&
563  
    {
563  
    {
564  
        if constexpr (InheritStopToken)
564  
        if constexpr (InheritStopToken)
565  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
565  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
566  
                mr_, std::move(t)};
566  
                mr_, std::move(t)};
567  
        else
567  
        else
568  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
568  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
569  
                mr_, std::move(t), std::move(st_)};
569  
                mr_, std::move(t), std::move(st_)};
570  
    }
570  
    }
571  
};
571  
};
572  

572  

573  
/// Specialization for stop_token only (no allocator).
573  
/// Specialization for stop_token only (no allocator).
574  
template<>
574  
template<>
575  
class [[nodiscard]] run_wrapper<false, void>
575  
class [[nodiscard]] run_wrapper<false, void>
576  
{
576  
{
577  
    std::stop_token st_;
577  
    std::stop_token st_;
578  

578  

579  
public:
579  
public:
580  
    explicit run_wrapper(std::stop_token st)
580  
    explicit run_wrapper(std::stop_token st)
581  
        : st_(std::move(st))
581  
        : st_(std::move(st))
582  
    {
582  
    {
583  
    }
583  
    }
584  

584  

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

590  

591  
    template<IoRunnable Task>
591  
    template<IoRunnable Task>
592  
    [[nodiscard]] auto operator()(Task t) &&
592  
    [[nodiscard]] auto operator()(Task t) &&
593  
    {
593  
    {
594  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
594  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
595  
    }
595  
    }
596  
};
596  
};
597  

597  

598  
} // namespace boost::capy::detail
598  
} // namespace boost::capy::detail
599  

599  

600  
namespace boost::capy {
600  
namespace boost::capy {
601  

601  

602  
/** Bind a task to execute on a specific executor.
602  
/** Bind a task to execute on a specific executor.
603  

603  

604  
    Returns a wrapper that accepts a task and produces an awaitable.
604  
    Returns a wrapper that accepts a task and produces an awaitable.
605  
    When co_awaited, the task runs on the specified executor.
605  
    When co_awaited, the task runs on the specified executor.
606  

606  

607  
    @par Example
607  
    @par Example
608  
    @code
608  
    @code
609  
    co_await run(other_executor)(my_task());
609  
    co_await run(other_executor)(my_task());
610  
    @endcode
610  
    @endcode
611  

611  

612  
    @param ex The executor on which the task should run.
612  
    @param ex The executor on which the task should run.
613  

613  

614  
    @return A wrapper that accepts a task for execution.
614  
    @return A wrapper that accepts a task for execution.
615  

615  

616  
    @see task
616  
    @see task
617  
    @see executor
617  
    @see executor
618  
*/
618  
*/
619  
template<Executor Ex>
619  
template<Executor Ex>
620  
[[nodiscard]] auto
620  
[[nodiscard]] auto
621  
run(Ex ex)
621  
run(Ex ex)
622  
{
622  
{
623  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
623  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
624  
}
624  
}
625  

625  

626  
/** Bind a task to an executor with a stop token.
626  
/** Bind a task to an executor with a stop token.
627  

627  

628  
    @param ex The executor on which the task should run.
628  
    @param ex The executor on which the task should run.
629  
    @param st The stop token for cooperative cancellation.
629  
    @param st The stop token for cooperative cancellation.
630  

630  

631  
    @return A wrapper that accepts a task for execution.
631  
    @return A wrapper that accepts a task for execution.
632  
*/
632  
*/
633  
template<Executor Ex>
633  
template<Executor Ex>
634  
[[nodiscard]] auto
634  
[[nodiscard]] auto
635  
run(Ex ex, std::stop_token st)
635  
run(Ex ex, std::stop_token st)
636  
{
636  
{
637  
    return detail::run_wrapper_ex<Ex, false, void>{
637  
    return detail::run_wrapper_ex<Ex, false, void>{
638  
        std::move(ex), std::move(st)};
638  
        std::move(ex), std::move(st)};
639  
}
639  
}
640  

640  

641  
/** Bind a task to an executor with a memory resource.
641  
/** Bind a task to an executor with a memory resource.
642  

642  

643  
    @param ex The executor on which the task should run.
643  
    @param ex The executor on which the task should run.
644  
    @param mr The memory resource for frame allocation.
644  
    @param mr The memory resource for frame allocation.
645  

645  

646  
    @return A wrapper that accepts a task for execution.
646  
    @return A wrapper that accepts a task for execution.
647  
*/
647  
*/
648  
template<Executor Ex>
648  
template<Executor Ex>
649  
[[nodiscard]] auto
649  
[[nodiscard]] auto
650  
run(Ex ex, std::pmr::memory_resource* mr)
650  
run(Ex ex, std::pmr::memory_resource* mr)
651  
{
651  
{
652  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
652  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
653  
        std::move(ex), mr};
653  
        std::move(ex), mr};
654  
}
654  
}
655  

655  

656  
/** Bind a task to an executor with a standard allocator.
656  
/** Bind a task to an executor with a standard allocator.
657  

657  

658  
    @param ex The executor on which the task should run.
658  
    @param ex The executor on which the task should run.
659  
    @param alloc The allocator for frame allocation.
659  
    @param alloc The allocator for frame allocation.
660  

660  

661  
    @return A wrapper that accepts a task for execution.
661  
    @return A wrapper that accepts a task for execution.
662  
*/
662  
*/
663  
template<Executor Ex, detail::Allocator Alloc>
663  
template<Executor Ex, detail::Allocator Alloc>
664  
[[nodiscard]] auto
664  
[[nodiscard]] auto
665  
run(Ex ex, Alloc alloc)
665  
run(Ex ex, Alloc alloc)
666  
{
666  
{
667  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
667  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
668  
        std::move(ex), std::move(alloc)};
668  
        std::move(ex), std::move(alloc)};
669  
}
669  
}
670  

670  

671  
/** Bind a task to an executor with stop token and memory resource.
671  
/** Bind a task to an executor with stop token and memory resource.
672  

672  

673  
    @param ex The executor on which the task should run.
673  
    @param ex The executor on which the task should run.
674  
    @param st The stop token for cooperative cancellation.
674  
    @param st The stop token for cooperative cancellation.
675  
    @param mr The memory resource for frame allocation.
675  
    @param mr The memory resource for frame allocation.
676  

676  

677  
    @return A wrapper that accepts a task for execution.
677  
    @return A wrapper that accepts a task for execution.
678  
*/
678  
*/
679  
template<Executor Ex>
679  
template<Executor Ex>
680  
[[nodiscard]] auto
680  
[[nodiscard]] auto
681  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
681  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
682  
{
682  
{
683  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
683  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
684  
        std::move(ex), std::move(st), mr};
684  
        std::move(ex), std::move(st), mr};
685  
}
685  
}
686  

686  

687  
/** Bind a task to an executor with stop token and standard allocator.
687  
/** Bind a task to an executor with stop token and standard allocator.
688  

688  

689  
    @param ex The executor on which the task should run.
689  
    @param ex The executor on which the task should run.
690  
    @param st The stop token for cooperative cancellation.
690  
    @param st The stop token for cooperative cancellation.
691  
    @param alloc The allocator for frame allocation.
691  
    @param alloc The allocator for frame allocation.
692  

692  

693  
    @return A wrapper that accepts a task for execution.
693  
    @return A wrapper that accepts a task for execution.
694  
*/
694  
*/
695  
template<Executor Ex, detail::Allocator Alloc>
695  
template<Executor Ex, detail::Allocator Alloc>
696  
[[nodiscard]] auto
696  
[[nodiscard]] auto
697  
run(Ex ex, std::stop_token st, Alloc alloc)
697  
run(Ex ex, std::stop_token st, Alloc alloc)
698  
{
698  
{
699  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
699  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
700  
        std::move(ex), std::move(st), std::move(alloc)};
700  
        std::move(ex), std::move(st), std::move(alloc)};
701  
}
701  
}
702  

702  

703  
/** Run a task with a custom stop token.
703  
/** Run a task with a custom stop token.
704  

704  

705  
    The task inherits the caller's executor. Only the stop token
705  
    The task inherits the caller's executor. Only the stop token
706  
    is overridden.
706  
    is overridden.
707  

707  

708  
    @par Example
708  
    @par Example
709  
    @code
709  
    @code
710  
    std::stop_source source;
710  
    std::stop_source source;
711  
    co_await run(source.get_token())(cancellable_task());
711  
    co_await run(source.get_token())(cancellable_task());
712  
    @endcode
712  
    @endcode
713  

713  

714  
    @param st The stop token for cooperative cancellation.
714  
    @param st The stop token for cooperative cancellation.
715  

715  

716  
    @return A wrapper that accepts a task for execution.
716  
    @return A wrapper that accepts a task for execution.
717  
*/
717  
*/
718  
[[nodiscard]] inline auto
718  
[[nodiscard]] inline auto
719  
run(std::stop_token st)
719  
run(std::stop_token st)
720  
{
720  
{
721  
    return detail::run_wrapper<false, void>{std::move(st)};
721  
    return detail::run_wrapper<false, void>{std::move(st)};
722  
}
722  
}
723  

723  

724  
/** Run a task with a custom memory resource.
724  
/** Run a task with a custom memory resource.
725  

725  

726  
    The task inherits the caller's executor. The memory resource
726  
    The task inherits the caller's executor. The memory resource
727  
    is used for nested frame allocations.
727  
    is used for nested frame allocations.
728  

728  

729  
    @param mr The memory resource for frame allocation.
729  
    @param mr The memory resource for frame allocation.
730  

730  

731  
    @return A wrapper that accepts a task for execution.
731  
    @return A wrapper that accepts a task for execution.
732  
*/
732  
*/
733  
[[nodiscard]] inline auto
733  
[[nodiscard]] inline auto
734  
run(std::pmr::memory_resource* mr)
734  
run(std::pmr::memory_resource* mr)
735  
{
735  
{
736  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
736  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
737  
}
737  
}
738  

738  

739  
/** Run a task with a custom standard allocator.
739  
/** Run a task with a custom standard allocator.
740  

740  

741  
    The task inherits the caller's executor. The allocator is used
741  
    The task inherits the caller's executor. The allocator is used
742  
    for nested frame allocations.
742  
    for nested frame allocations.
743  

743  

744  
    @param alloc The allocator for frame allocation.
744  
    @param alloc The allocator for frame allocation.
745  

745  

746  
    @return A wrapper that accepts a task for execution.
746  
    @return A wrapper that accepts a task for execution.
747  
*/
747  
*/
748  
template<detail::Allocator Alloc>
748  
template<detail::Allocator Alloc>
749  
[[nodiscard]] auto
749  
[[nodiscard]] auto
750  
run(Alloc alloc)
750  
run(Alloc alloc)
751  
{
751  
{
752  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
752  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
753  
}
753  
}
754  

754  

755  
/** Run a task with stop token and memory resource.
755  
/** Run a task with stop token and memory resource.
756  

756  

757  
    The task inherits the caller's executor.
757  
    The task inherits the caller's executor.
758  

758  

759  
    @param st The stop token for cooperative cancellation.
759  
    @param st The stop token for cooperative cancellation.
760  
    @param mr The memory resource for frame allocation.
760  
    @param mr The memory resource for frame allocation.
761  

761  

762  
    @return A wrapper that accepts a task for execution.
762  
    @return A wrapper that accepts a task for execution.
763  
*/
763  
*/
764  
[[nodiscard]] inline auto
764  
[[nodiscard]] inline auto
765  
run(std::stop_token st, std::pmr::memory_resource* mr)
765  
run(std::stop_token st, std::pmr::memory_resource* mr)
766  
{
766  
{
767  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
767  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
768  
        std::move(st), mr};
768  
        std::move(st), mr};
769  
}
769  
}
770  

770  

771  
/** Run a task with stop token and standard allocator.
771  
/** Run a task with stop token and standard allocator.
772  

772  

773  
    The task inherits the caller's executor.
773  
    The task inherits the caller's executor.
774  

774  

775  
    @param st The stop token for cooperative cancellation.
775  
    @param st The stop token for cooperative cancellation.
776  
    @param alloc The allocator for frame allocation.
776  
    @param alloc The allocator for frame allocation.
777  

777  

778  
    @return A wrapper that accepts a task for execution.
778  
    @return A wrapper that accepts a task for execution.
779  
*/
779  
*/
780  
template<detail::Allocator Alloc>
780  
template<detail::Allocator Alloc>
781  
[[nodiscard]] auto
781  
[[nodiscard]] auto
782  
run(std::stop_token st, Alloc alloc)
782  
run(std::stop_token st, Alloc alloc)
783  
{
783  
{
784  
    return detail::run_wrapper<false, Alloc>{
784  
    return detail::run_wrapper<false, Alloc>{
785  
        std::move(st), std::move(alloc)};
785  
        std::move(st), std::move(alloc)};
786  
}
786  
}
787  

787  

788  
} // namespace boost::capy
788  
} // namespace boost::capy
789  

789  

790  
#endif
790  
#endif