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_alloc_mixin.hpp>
20  
#include <boost/capy/ex/frame_allocator.hpp>
21  
#include <boost/capy/ex/frame_allocator.hpp>
21  
#include <boost/capy/ex/io_env.hpp>
22  
#include <boost/capy/ex/io_env.hpp>
22  

23  

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

29  

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

33  

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

35  

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

37  

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

45  

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

49  

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

54  

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

60  

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

62  

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

64  

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

70  

70  
    The trampoline never touches the task's result.
71  
    The trampoline never touches the task's result.
71  
*/
72  
*/
72 -
struct dispatch_trampoline
73 +
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE dispatch_trampoline
73  
{
74  
{
74  
    struct promise_type
75  
    struct promise_type
 
76 +
        : frame_alloc_mixin
75  
    {
77  
    {
76  
        executor_ref caller_ex_;
78  
        executor_ref caller_ex_;
77  
        continuation parent_;
79  
        continuation parent_;
78  

80  

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

86  

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

88  

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

95  

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

102  

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

107  

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

111  

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

113  

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

115  

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

120  

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

123  

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

126  

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

136  

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

141  

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

146  

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

148  

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

152  

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

156  

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

160  

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

176  

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

184  

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

193  

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

203  

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

214  

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

219  

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

224  

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

230  

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

234  

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

240  

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

245  

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

250  

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

254  

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

259  

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

261  

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

266  

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

278  

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

285  

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

293  

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

302  

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

312  

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

317  

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

322  

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

328  

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

334  

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

339  

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

343  

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

347  

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

352  

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

354  

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

366  

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

376  

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

386  

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

392  

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

404  

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

412  

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

421  

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

430  

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

436  

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

448  

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

455  

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

462  

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

469  

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

475  

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

487  

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

489  

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

499  

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

508  

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

517  

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

523  

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

535  

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

542  

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

550  

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

558  

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

564  

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

576  

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

582  

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

588  

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

594  

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

601  

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

603  

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

605  

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

607  

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

610  

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

615  

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

617  

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

619  

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

629  

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

631  

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

634  

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

644  

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

646  

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

649  

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

659  

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

661  

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

664  

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

674  

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

676  

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

680  

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

690  

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

692  

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

696  

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

706  

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

708  

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

711  

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

717  

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

719  

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

727  

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

729  

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

732  

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

734  

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

742  

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

744  

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

747  

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

749  

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

758  

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

760  

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

762  

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

765  

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

774  

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

776  

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

778  

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

781  

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

791  

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

793  

792  
#endif
794  
#endif