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

22  

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

28  

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

32  

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

34  

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

36  

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

44  

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

48  

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

53  

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

59  

59  
namespace boost::capy::detail {
60  
namespace boost::capy::detail {
60  

61  

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

67  

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

69  

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

75  

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

84  

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

90  

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

92  

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

99  

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

106  

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

111  

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

115  

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

117  

116  
    dispatch_trampoline() noexcept = default;
118  
    dispatch_trampoline() noexcept = default;
117  

119  

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

124  

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

127  

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

130  

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

140  

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

145  

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

150  

149  
//----------------------------------------------------------
151  
//----------------------------------------------------------
150  
//
152  
//
151  
// run_awaitable_ex - with executor (executor switch)
153  
// run_awaitable_ex - with executor (executor switch)
152  
//
154  
//
153  
//----------------------------------------------------------
155  
//----------------------------------------------------------
154  

156  

155  
/** Awaitable that binds an IoRunnable to a specific executor.
157  
/** Awaitable that binds an IoRunnable to a specific executor.
156  

158  

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

162  

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

166  

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

170  

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

185  

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

193  

192  
    // void allocator, explicit stop token
194  
    // void allocator, explicit stop token
193  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
195  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
194  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
196  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
195  
        : ex_(std::move(ex))
197  
        : ex_(std::move(ex))
196  
        , st_(std::move(st))
198  
        , st_(std::move(st))
197  
        , inner_(std::move(inner))
199  
        , inner_(std::move(inner))
198  
    {
200  
    {
199  
    }
201  
    }
200  

202  

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

212  

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

223  

222  
    bool await_ready() const noexcept
224  
    bool await_ready() const noexcept
223  
    {
225  
    {
224  
        return inner_.await_ready();
226  
        return inner_.await_ready();
225  
    }
227  
    }
226  

228  

227  
    decltype(auto) await_resume()
229  
    decltype(auto) await_resume()
228  
    {
230  
    {
229  
        return inner_.await_resume();
231  
        return inner_.await_resume();
230  
    }
232  
    }
231  

233  

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

239  

238  
        auto h = inner_.handle();
240  
        auto h = inner_.handle();
239  
        auto& p = h.promise();
241  
        auto& p = h.promise();
240  
        p.set_continuation(tr_.h_);
242  
        p.set_continuation(tr_.h_);
241  

243  

242  
        env_.executor = ex_;
244  
        env_.executor = ex_;
243  
        if constexpr (InheritStopToken)
245  
        if constexpr (InheritStopToken)
244  
            env_.stop_token = caller_env->stop_token;
246  
            env_.stop_token = caller_env->stop_token;
245  
        else
247  
        else
246  
            env_.stop_token = st_;
248  
            env_.stop_token = st_;
247  

249  

248  
        if constexpr (!std::is_void_v<Alloc>)
250  
        if constexpr (!std::is_void_v<Alloc>)
249  
            env_.frame_allocator = resource_.get();
251  
            env_.frame_allocator = resource_.get();
250  
        else
252  
        else
251  
            env_.frame_allocator = caller_env->frame_allocator;
253  
            env_.frame_allocator = caller_env->frame_allocator;
252  

254  

253  
        p.set_environment(&env_);
255  
        p.set_environment(&env_);
254  
        return h;
256  
        return h;
255  
    }
257  
    }
256  

258  

257  
    // Non-copyable
259  
    // Non-copyable
258  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
260  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
259  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
261  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
260  

262  

261  
    // Movable (no noexcept - Task may throw)
263  
    // Movable (no noexcept - Task may throw)
262  
    run_awaitable_ex(run_awaitable_ex&&) = default;
264  
    run_awaitable_ex(run_awaitable_ex&&) = default;
263  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
265  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
264  
};
266  
};
265  

267  

266  
//----------------------------------------------------------
268  
//----------------------------------------------------------
267  
//
269  
//
268  
// run_awaitable - no executor (inherits caller's executor)
270  
// run_awaitable - no executor (inherits caller's executor)
269  
//
271  
//
270  
//----------------------------------------------------------
272  
//----------------------------------------------------------
271  

273  

272  
/** Awaitable that runs a task with optional stop_token override.
274  
/** Awaitable that runs a task with optional stop_token override.
273  

275  

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

280  

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

292  

291  
    // void allocator, inherit stop token
293  
    // void allocator, inherit stop token
292  
    explicit run_awaitable(Task inner)
294  
    explicit run_awaitable(Task inner)
293  
        requires (InheritStopToken && std::is_void_v<Alloc>)
295  
        requires (InheritStopToken && std::is_void_v<Alloc>)
294  
        : inner_(std::move(inner))
296  
        : inner_(std::move(inner))
295  
    {
297  
    {
296  
    }
298  
    }
297  

299  

298  
    // void allocator, explicit stop token
300  
    // void allocator, explicit stop token
299  
    run_awaitable(Task inner, std::stop_token st)
301  
    run_awaitable(Task inner, std::stop_token st)
300  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
302  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
301  
        : st_(std::move(st))
303  
        : st_(std::move(st))
302  
        , inner_(std::move(inner))
304  
        , inner_(std::move(inner))
303  
    {
305  
    {
304  
    }
306  
    }
305  

307  

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

316  

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

326  

325  
    bool await_ready() const noexcept
327  
    bool await_ready() const noexcept
326  
    {
328  
    {
327  
        return inner_.await_ready();
329  
        return inner_.await_ready();
328  
    }
330  
    }
329  

331  

330  
    decltype(auto) await_resume()
332  
    decltype(auto) await_resume()
331  
    {
333  
    {
332  
        return inner_.await_resume();
334  
        return inner_.await_resume();
333  
    }
335  
    }
334  

336  

335  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
337  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
336  
    {
338  
    {
337  
        auto h = inner_.handle();
339  
        auto h = inner_.handle();
338  
        auto& p = h.promise();
340  
        auto& p = h.promise();
339  
        p.set_continuation(cont);
341  
        p.set_continuation(cont);
340  

342  

341  
        env_.executor = caller_env->executor;
343  
        env_.executor = caller_env->executor;
342  
        if constexpr (InheritStopToken)
344  
        if constexpr (InheritStopToken)
343  
            env_.stop_token = caller_env->stop_token;
345  
            env_.stop_token = caller_env->stop_token;
344  
        else
346  
        else
345  
            env_.stop_token = st_;
347  
            env_.stop_token = st_;
346  

348  

347  
        if constexpr (!std::is_void_v<Alloc>)
349  
        if constexpr (!std::is_void_v<Alloc>)
348  
            env_.frame_allocator = resource_.get();
350  
            env_.frame_allocator = resource_.get();
349  
        else
351  
        else
350  
            env_.frame_allocator = caller_env->frame_allocator;
352  
            env_.frame_allocator = caller_env->frame_allocator;
351  

353  

352  
        p.set_environment(&env_);
354  
        p.set_environment(&env_);
353  
        return h;
355  
        return h;
354  
    }
356  
    }
355  

357  

356  
    // Non-copyable
358  
    // Non-copyable
357  
    run_awaitable(run_awaitable const&) = delete;
359  
    run_awaitable(run_awaitable const&) = delete;
358  
    run_awaitable& operator=(run_awaitable const&) = delete;
360  
    run_awaitable& operator=(run_awaitable const&) = delete;
359  

361  

360  
    // Movable (no noexcept - Task may throw)
362  
    // Movable (no noexcept - Task may throw)
361  
    run_awaitable(run_awaitable&&) = default;
363  
    run_awaitable(run_awaitable&&) = default;
362  
    run_awaitable& operator=(run_awaitable&&) = default;
364  
    run_awaitable& operator=(run_awaitable&&) = default;
363  
};
365  
};
364  

366  

365  
//----------------------------------------------------------
367  
//----------------------------------------------------------
366  
//
368  
//
367  
// run_wrapper_ex - with executor
369  
// run_wrapper_ex - with executor
368  
//
370  
//
369  
//----------------------------------------------------------
371  
//----------------------------------------------------------
370  

372  

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

374  

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

386  

385  
public:
387  
public:
386  
    run_wrapper_ex(Ex ex, Alloc alloc)
388  
    run_wrapper_ex(Ex ex, Alloc alloc)
387  
        requires InheritStopToken
389  
        requires InheritStopToken
388  
        : ex_(std::move(ex))
390  
        : ex_(std::move(ex))
389  
        , resource_(alloc)
391  
        , resource_(alloc)
390  
        , alloc_(std::move(alloc))
392  
        , alloc_(std::move(alloc))
391  
    {
393  
    {
392  
        set_current_frame_allocator(&resource_);
394  
        set_current_frame_allocator(&resource_);
393  
    }
395  
    }
394  

396  

395  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
397  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
396  
        requires (!InheritStopToken)
398  
        requires (!InheritStopToken)
397  
        : ex_(std::move(ex))
399  
        : ex_(std::move(ex))
398  
        , st_(std::move(st))
400  
        , st_(std::move(st))
399  
        , resource_(alloc)
401  
        , resource_(alloc)
400  
        , alloc_(std::move(alloc))
402  
        , alloc_(std::move(alloc))
401  
    {
403  
    {
402  
        set_current_frame_allocator(&resource_);
404  
        set_current_frame_allocator(&resource_);
403  
    }
405  
    }
404  

406  

405  
    // Non-copyable, non-movable (must be used immediately)
407  
    // Non-copyable, non-movable (must be used immediately)
406  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
408  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
407  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
409  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
408  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
410  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
409  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
411  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
410  

412  

411  
    template<IoRunnable Task>
413  
    template<IoRunnable Task>
412  
    [[nodiscard]] auto operator()(Task t) &&
414  
    [[nodiscard]] auto operator()(Task t) &&
413  
    {
415  
    {
414  
        if constexpr (InheritStopToken)
416  
        if constexpr (InheritStopToken)
415  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
417  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
416  
                std::move(ex_), std::move(alloc_), std::move(t)};
418  
                std::move(ex_), std::move(alloc_), std::move(t)};
417  
        else
419  
        else
418  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
420  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
419  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
421  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
420  
    }
422  
    }
421  
};
423  
};
422  

424  

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

432  

431  
public:
433  
public:
432  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
434  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
433  
        requires InheritStopToken
435  
        requires InheritStopToken
434  
        : ex_(std::move(ex))
436  
        : ex_(std::move(ex))
435  
        , mr_(mr)
437  
        , mr_(mr)
436  
    {
438  
    {
437  
        set_current_frame_allocator(mr_);
439  
        set_current_frame_allocator(mr_);
438  
    }
440  
    }
439  

441  

440  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
442  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
441  
        requires (!InheritStopToken)
443  
        requires (!InheritStopToken)
442  
        : ex_(std::move(ex))
444  
        : ex_(std::move(ex))
443  
        , st_(std::move(st))
445  
        , st_(std::move(st))
444  
        , mr_(mr)
446  
        , mr_(mr)
445  
    {
447  
    {
446  
        set_current_frame_allocator(mr_);
448  
        set_current_frame_allocator(mr_);
447  
    }
449  
    }
448  

450  

449  
    // Non-copyable, non-movable (must be used immediately)
451  
    // Non-copyable, non-movable (must be used immediately)
450  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
452  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
451  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
453  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
452  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
454  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
453  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
455  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
454  

456  

455  
    template<IoRunnable Task>
457  
    template<IoRunnable Task>
456  
    [[nodiscard]] auto operator()(Task t) &&
458  
    [[nodiscard]] auto operator()(Task t) &&
457  
    {
459  
    {
458  
        if constexpr (InheritStopToken)
460  
        if constexpr (InheritStopToken)
459  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
461  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
460  
                std::move(ex_), mr_, std::move(t)};
462  
                std::move(ex_), mr_, std::move(t)};
461  
        else
463  
        else
462  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
464  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
463  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
465  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
464  
    }
466  
    }
465  
};
467  
};
466  

468  

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

475  

474  
public:
476  
public:
475  
    explicit run_wrapper_ex(Ex ex)
477  
    explicit run_wrapper_ex(Ex ex)
476  
        requires InheritStopToken
478  
        requires InheritStopToken
477  
        : ex_(std::move(ex))
479  
        : ex_(std::move(ex))
478  
    {
480  
    {
479  
    }
481  
    }
480  

482  

481  
    run_wrapper_ex(Ex ex, std::stop_token st)
483  
    run_wrapper_ex(Ex ex, std::stop_token st)
482  
        requires (!InheritStopToken)
484  
        requires (!InheritStopToken)
483  
        : ex_(std::move(ex))
485  
        : ex_(std::move(ex))
484  
        , st_(std::move(st))
486  
        , st_(std::move(st))
485  
    {
487  
    {
486  
    }
488  
    }
487  

489  

488  
    // Non-copyable, non-movable (must be used immediately)
490  
    // Non-copyable, non-movable (must be used immediately)
489  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
491  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
490  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
492  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
491  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
493  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
492  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
494  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
493  

495  

494  
    template<IoRunnable Task>
496  
    template<IoRunnable Task>
495  
    [[nodiscard]] auto operator()(Task t) &&
497  
    [[nodiscard]] auto operator()(Task t) &&
496  
    {
498  
    {
497  
        if constexpr (InheritStopToken)
499  
        if constexpr (InheritStopToken)
498  
            return run_awaitable_ex<Task, Ex, true>{
500  
            return run_awaitable_ex<Task, Ex, true>{
499  
                std::move(ex_), std::move(t)};
501  
                std::move(ex_), std::move(t)};
500  
        else
502  
        else
501  
            return run_awaitable_ex<Task, Ex, false>{
503  
            return run_awaitable_ex<Task, Ex, false>{
502  
                std::move(ex_), std::move(t), std::move(st_)};
504  
                std::move(ex_), std::move(t), std::move(st_)};
503  
    }
505  
    }
504  
};
506  
};
505  

507  

506  
//----------------------------------------------------------
508  
//----------------------------------------------------------
507  
//
509  
//
508  
// run_wrapper - no executor (inherits caller's executor)
510  
// run_wrapper - no executor (inherits caller's executor)
509  
//
511  
//
510  
//----------------------------------------------------------
512  
//----------------------------------------------------------
511  

513  

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

515  

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

525  

524  
public:
526  
public:
525  
    explicit run_wrapper(Alloc alloc)
527  
    explicit run_wrapper(Alloc alloc)
526  
        requires InheritStopToken
528  
        requires InheritStopToken
527  
        : resource_(alloc)
529  
        : resource_(alloc)
528  
        , alloc_(std::move(alloc))
530  
        , alloc_(std::move(alloc))
529  
    {
531  
    {
530  
        set_current_frame_allocator(&resource_);
532  
        set_current_frame_allocator(&resource_);
531  
    }
533  
    }
532  

534  

533  
    run_wrapper(std::stop_token st, Alloc alloc)
535  
    run_wrapper(std::stop_token st, Alloc alloc)
534  
        requires (!InheritStopToken)
536  
        requires (!InheritStopToken)
535  
        : st_(std::move(st))
537  
        : st_(std::move(st))
536  
        , resource_(alloc)
538  
        , resource_(alloc)
537  
        , alloc_(std::move(alloc))
539  
        , alloc_(std::move(alloc))
538  
    {
540  
    {
539  
        set_current_frame_allocator(&resource_);
541  
        set_current_frame_allocator(&resource_);
540  
    }
542  
    }
541  

543  

542  
    // Non-copyable, non-movable (must be used immediately)
544  
    // Non-copyable, non-movable (must be used immediately)
543  
    run_wrapper(run_wrapper const&) = delete;
545  
    run_wrapper(run_wrapper const&) = delete;
544  
    run_wrapper(run_wrapper&&) = delete;
546  
    run_wrapper(run_wrapper&&) = delete;
545  
    run_wrapper& operator=(run_wrapper const&) = delete;
547  
    run_wrapper& operator=(run_wrapper const&) = delete;
546  
    run_wrapper& operator=(run_wrapper&&) = delete;
548  
    run_wrapper& operator=(run_wrapper&&) = delete;
547  

549  

548  
    template<IoRunnable Task>
550  
    template<IoRunnable Task>
549  
    [[nodiscard]] auto operator()(Task t) &&
551  
    [[nodiscard]] auto operator()(Task t) &&
550  
    {
552  
    {
551  
        if constexpr (InheritStopToken)
553  
        if constexpr (InheritStopToken)
552  
            return run_awaitable<Task, true, Alloc>{
554  
            return run_awaitable<Task, true, Alloc>{
553  
                std::move(alloc_), std::move(t)};
555  
                std::move(alloc_), std::move(t)};
554  
        else
556  
        else
555  
            return run_awaitable<Task, false, Alloc>{
557  
            return run_awaitable<Task, false, Alloc>{
556  
                std::move(alloc_), std::move(t), std::move(st_)};
558  
                std::move(alloc_), std::move(t), std::move(st_)};
557  
    }
559  
    }
558  
};
560  
};
559  

561  

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

568  

567  
public:
569  
public:
568  
    explicit run_wrapper(std::pmr::memory_resource* mr)
570  
    explicit run_wrapper(std::pmr::memory_resource* mr)
569  
        requires InheritStopToken
571  
        requires InheritStopToken
570  
        : mr_(mr)
572  
        : mr_(mr)
571  
    {
573  
    {
572  
        set_current_frame_allocator(mr_);
574  
        set_current_frame_allocator(mr_);
573  
    }
575  
    }
574  

576  

575  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
577  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
576  
        requires (!InheritStopToken)
578  
        requires (!InheritStopToken)
577  
        : st_(std::move(st))
579  
        : st_(std::move(st))
578  
        , mr_(mr)
580  
        , mr_(mr)
579  
    {
581  
    {
580  
        set_current_frame_allocator(mr_);
582  
        set_current_frame_allocator(mr_);
581  
    }
583  
    }
582  

584  

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

590  

589  
    template<IoRunnable Task>
591  
    template<IoRunnable Task>
590  
    [[nodiscard]] auto operator()(Task t) &&
592  
    [[nodiscard]] auto operator()(Task t) &&
591  
    {
593  
    {
592  
        if constexpr (InheritStopToken)
594  
        if constexpr (InheritStopToken)
593  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
595  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
594  
                mr_, std::move(t)};
596  
                mr_, std::move(t)};
595  
        else
597  
        else
596  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
598  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
597  
                mr_, std::move(t), std::move(st_)};
599  
                mr_, std::move(t), std::move(st_)};
598  
    }
600  
    }
599  
};
601  
};
600  

602  

601  
/// Specialization for stop_token only (no allocator).
603  
/// Specialization for stop_token only (no allocator).
602  
template<>
604  
template<>
603  
class [[nodiscard]] run_wrapper<false, void>
605  
class [[nodiscard]] run_wrapper<false, void>
604  
{
606  
{
605  
    std::stop_token st_;
607  
    std::stop_token st_;
606  

608  

607  
public:
609  
public:
608  
    explicit run_wrapper(std::stop_token st)
610  
    explicit run_wrapper(std::stop_token st)
609  
        : st_(std::move(st))
611  
        : st_(std::move(st))
610  
    {
612  
    {
611  
    }
613  
    }
612  

614  

613  
    // Non-copyable, non-movable (must be used immediately)
615  
    // Non-copyable, non-movable (must be used immediately)
614  
    run_wrapper(run_wrapper const&) = delete;
616  
    run_wrapper(run_wrapper const&) = delete;
615  
    run_wrapper(run_wrapper&&) = delete;
617  
    run_wrapper(run_wrapper&&) = delete;
616  
    run_wrapper& operator=(run_wrapper const&) = delete;
618  
    run_wrapper& operator=(run_wrapper const&) = delete;
617  
    run_wrapper& operator=(run_wrapper&&) = delete;
619  
    run_wrapper& operator=(run_wrapper&&) = delete;
618  

620  

619  
    template<IoRunnable Task>
621  
    template<IoRunnable Task>
620  
    [[nodiscard]] auto operator()(Task t) &&
622  
    [[nodiscard]] auto operator()(Task t) &&
621  
    {
623  
    {
622  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
624  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
623  
    }
625  
    }
624  
};
626  
};
625  

627  

626  
} // namespace boost::capy::detail
628  
} // namespace boost::capy::detail
627  

629  

628  
namespace boost::capy {
630  
namespace boost::capy {
629  

631  

630  
//----------------------------------------------------------
632  
//----------------------------------------------------------
631  
//
633  
//
632  
// run() overloads - with executor
634  
// run() overloads - with executor
633  
//
635  
//
634  
//----------------------------------------------------------
636  
//----------------------------------------------------------
635  

637  

636  
/** Bind a task to execute on a specific executor.
638  
/** Bind a task to execute on a specific executor.
637  

639  

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

642  

641  
    @par Example
643  
    @par Example
642  
    @code
644  
    @code
643  
    co_await run(other_executor)(my_task());
645  
    co_await run(other_executor)(my_task());
644  
    @endcode
646  
    @endcode
645  

647  

646  
    @param ex The executor on which the task should run.
648  
    @param ex The executor on which the task should run.
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  
    @see task
652  
    @see task
651  
    @see executor
653  
    @see executor
652  
*/
654  
*/
653  
template<Executor Ex>
655  
template<Executor Ex>
654  
[[nodiscard]] auto
656  
[[nodiscard]] auto
655  
run(Ex ex)
657  
run(Ex ex)
656  
{
658  
{
657  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
659  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
658  
}
660  
}
659  

661  

660  
/** Bind a task to an executor with a stop token.
662  
/** Bind a task to an executor with a stop token.
661  

663  

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

666  

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

676  

675  
/** Bind a task to an executor with a memory resource.
677  
/** Bind a task to an executor with a memory resource.
676  

678  

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

681  

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

691  

690  
/** Bind a task to an executor with a standard allocator.
692  
/** Bind a task to an executor with a standard allocator.
691  

693  

692  
    @param ex The executor on which the task should run.
694  
    @param ex The executor on which the task should run.
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, Alloc alloc)
701  
run(Ex ex, Alloc alloc)
700  
{
702  
{
701  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
703  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
702  
        std::move(ex), std::move(alloc)};
704  
        std::move(ex), std::move(alloc)};
703  
}
705  
}
704  

706  

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

708  

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

712  

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

722  

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

724  

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

728  

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

738  

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

744  

743  
/** Run a task with a custom stop token.
745  
/** Run a task with a custom stop token.
744  

746  

745  
    The task inherits the caller's executor. Only the stop token
747  
    The task inherits the caller's executor. Only the stop token
746  
    is overridden.
748  
    is overridden.
747  

749  

748  
    @par Example
750  
    @par Example
749  
    @code
751  
    @code
750  
    std::stop_source source;
752  
    std::stop_source source;
751  
    co_await run(source.get_token())(cancellable_task());
753  
    co_await run(source.get_token())(cancellable_task());
752  
    @endcode
754  
    @endcode
753  

755  

754  
    @param st The stop token for cooperative cancellation.
756  
    @param st The stop token for cooperative cancellation.
755  

757  

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

765  

764  
/** Run a task with a custom memory resource.
766  
/** Run a task with a custom memory resource.
765  

767  

766  
    The task inherits the caller's executor. The memory resource
768  
    The task inherits the caller's executor. The memory resource
767  
    is used for nested frame allocations.
769  
    is used for nested frame allocations.
768  

770  

769  
    @param mr The memory resource for frame allocation.
771  
    @param mr The memory resource for frame allocation.
770  

772  

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

780  

779  
/** Run a task with a custom standard allocator.
781  
/** Run a task with a custom standard allocator.
780  

782  

781  
    The task inherits the caller's executor. The allocator is used
783  
    The task inherits the caller's executor. The allocator is used
782  
    for nested frame allocations.
784  
    for nested frame allocations.
783  

785  

784  
    @param alloc The allocator for frame allocation.
786  
    @param alloc The allocator for frame allocation.
785  

787  

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

796  

795  
/** Run a task with stop token and memory resource.
797  
/** Run a task with stop token and memory resource.
796  

798  

797  
    The task inherits the caller's executor.
799  
    The task inherits the caller's executor.
798  

800  

799  
    @param st The stop token for cooperative cancellation.
801  
    @param st The stop token for cooperative cancellation.
800  
    @param mr The memory resource for frame allocation.
802  
    @param mr The memory resource for frame allocation.
801  

803  

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

812  

811  
/** Run a task with stop token and standard allocator.
813  
/** Run a task with stop token and standard allocator.
812  

814  

813  
    The task inherits the caller's executor.
815  
    The task inherits the caller's executor.
814  

816  

815  
    @param st The stop token for cooperative cancellation.
817  
    @param st The stop token for cooperative cancellation.
816  
    @param alloc The allocator for frame allocation.
818  
    @param alloc The allocator for frame allocation.
817  

819  

818  
    @return A wrapper that accepts a task for execution.
820  
    @return A wrapper that accepts a task for execution.
819  
*/
821  
*/
820  
template<detail::Allocator Alloc>
822  
template<detail::Allocator Alloc>
821  
[[nodiscard]] auto
823  
[[nodiscard]] auto
822  
run(std::stop_token st, Alloc alloc)
824  
run(std::stop_token st, Alloc alloc)
823  
{
825  
{
824  
    return detail::run_wrapper<false, Alloc>{
826  
    return detail::run_wrapper<false, Alloc>{
825  
        std::move(st), std::move(alloc)};
827  
        std::move(st), std::move(alloc)};
826  
}
828  
}
827  

829  

828  
} // namespace boost::capy
830  
} // namespace boost::capy
829  

831  

830  
#endif
832  
#endif