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  
        continuation parent_;
77  
        continuation 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  
    continuation task_cont_;
172  
    continuation task_cont_;
173  
    Task inner_;  // Last: destroyed first, while env_ is still valid
173  
    Task inner_;  // Last: destroyed first, while env_ is still valid
174  

174  

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

182  

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

191  

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

201  

202  
    // with allocator, explicit stop token (use template to avoid void parameter)
202  
    // with allocator, explicit stop token (use template to avoid void parameter)
203  
    template<class A>
203  
    template<class A>
204  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
204  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
205  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
205  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
206  
        : ex_(std::move(ex))
206  
        : ex_(std::move(ex))
207  
        , resource_(std::move(alloc))
207  
        , resource_(std::move(alloc))
208  
        , st_(std::move(st))
208  
        , st_(std::move(st))
209  
        , inner_(std::move(inner))
209  
        , inner_(std::move(inner))
210  
    {
210  
    {
211  
    }
211  
    }
212  

212  

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

217  

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

222  

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

228  

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

232  

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

238  

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

243  

244  
        p.set_environment(&env_);
244  
        p.set_environment(&env_);
245  
        task_cont_.h = h;
245  
        task_cont_.h = h;
246  
        return ex_.dispatch(task_cont_);
246  
        return ex_.dispatch(task_cont_);
247  
    }
247  
    }
248  

248  

249  
    // Non-copyable
249  
    // Non-copyable
250  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
250  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
251  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
251  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
252  

252  

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

257  

258  
/** Awaitable that runs a task with optional stop_token override.
258  
/** Awaitable that runs a task with optional stop_token override.
259  

259  

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

264  

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

276  

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

283  

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

291  

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

300  

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

310  

311  
    bool await_ready() const noexcept
311  
    bool await_ready() const noexcept
312  
    {
312  
    {
313  
        return inner_.await_ready();
313  
        return inner_.await_ready();
314  
    }
314  
    }
315  

315  

316  
    decltype(auto) await_resume()
316  
    decltype(auto) await_resume()
317  
    {
317  
    {
318  
        return inner_.await_resume();
318  
        return inner_.await_resume();
319  
    }
319  
    }
320  

320  

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

326  

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

332  

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

337  

338  
        p.set_environment(&env_);
338  
        p.set_environment(&env_);
339  
        return h;
339  
        return h;
340  
    }
340  
    }
341  

341  

342  
    // Non-copyable
342  
    // Non-copyable
343  
    run_awaitable(run_awaitable const&) = delete;
343  
    run_awaitable(run_awaitable const&) = delete;
344  
    run_awaitable& operator=(run_awaitable const&) = delete;
344  
    run_awaitable& operator=(run_awaitable const&) = delete;
345  

345  

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

350  

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

352  

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

364  

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

374  

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

384  

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

390  

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

402  

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

410  

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

419  

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

428  

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

434  

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

446  

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

453  

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

460  

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

467  

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

473  

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

485  

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

487  

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

497  

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

506  

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

515  

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

521  

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

533  

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

540  

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

548  

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

556  

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

562  

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

574  

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

580  

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

586  

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

592  

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

599  

600  
} // namespace boost::capy::detail
600  
} // namespace boost::capy::detail
601  

601  

602  
namespace boost::capy {
602  
namespace boost::capy {
603  

603  

604  
/** Bind a task to execute on a specific executor.
604  
/** Bind a task to execute on a specific executor.
605  

605  

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

608  

609  
    @par Example
609  
    @par Example
610  
    @code
610  
    @code
611  
    co_await run(other_executor)(my_task());
611  
    co_await run(other_executor)(my_task());
612  
    @endcode
612  
    @endcode
613  

613  

614  
    @param ex The executor on which the task should run.
614  
    @param ex The executor on which the task should run.
615  

615  

616  
    @return A wrapper that accepts a task for execution.
616  
    @return A wrapper that accepts a task for execution.
617  

617  

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

627  

628  
/** Bind a task to an executor with a stop token.
628  
/** Bind a task to an executor with a stop token.
629  

629  

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

632  

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

642  

643  
/** Bind a task to an executor with a memory resource.
643  
/** Bind a task to an executor with a memory resource.
644  

644  

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

647  

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

657  

658  
/** Bind a task to an executor with a standard allocator.
658  
/** Bind a task to an executor with a standard allocator.
659  

659  

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

662  

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

672  

673  
/** Bind a task to an executor with stop token and memory resource.
673  
/** Bind a task to an executor with stop token and memory resource.
674  

674  

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

678  

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

688  

689  
/** Bind a task to an executor with stop token and standard allocator.
689  
/** Bind a task to an executor with stop token and standard allocator.
690  

690  

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

694  

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

704  

705  
/** Run a task with a custom stop token.
705  
/** Run a task with a custom stop token.
706  

706  

707  
    The task inherits the caller's executor. Only the stop token
707  
    The task inherits the caller's executor. Only the stop token
708  
    is overridden.
708  
    is overridden.
709  

709  

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

715  

716  
    @param st The stop token for cooperative cancellation.
716  
    @param st The stop token for cooperative cancellation.
717  

717  

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

725  

726  
/** Run a task with a custom memory resource.
726  
/** Run a task with a custom memory resource.
727  

727  

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

730  

731  
    @param mr The memory resource for frame allocation.
731  
    @param mr The memory resource for frame allocation.
732  

732  

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

740  

741  
/** Run a task with a custom standard allocator.
741  
/** Run a task with a custom standard allocator.
742  

742  

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

745  

746  
    @param alloc The allocator for frame allocation.
746  
    @param alloc The allocator for frame allocation.
747  

747  

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

756  

757  
/** Run a task with stop token and memory resource.
757  
/** Run a task with stop token and memory resource.
758  

758  

759  
    The task inherits the caller's executor.
759  
    The task inherits the caller's executor.
760  

760  

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

763  

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

772  

773  
/** Run a task with stop token and standard allocator.
773  
/** Run a task with stop token and standard allocator.
774  

774  

775  
    The task inherits the caller's executor.
775  
    The task inherits the caller's executor.
776  

776  

777  
    @param st The stop token for cooperative cancellation.
777  
    @param st The stop token for cooperative cancellation.
778  
    @param alloc The allocator for frame allocation.
778  
    @param alloc The allocator for frame allocation.
779  

779  

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

789  

790  
} // namespace boost::capy
790  
} // namespace boost::capy
791  

791  

792  
#endif
792  
#endif