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

21  

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

27  

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

31  

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

33  

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

35  

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

43  

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

47  

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

52  

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

58  

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

60  

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

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

68  

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

74  

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

83  

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

89  

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

91  

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

98  

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

104  

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

109  

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

113  

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

115  

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

117  

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

122  

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

125  

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

128  

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

138  

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

143  

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

148  

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

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

156  

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

160  

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

164  

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

168  

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

183  

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

191  

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

200  

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

210  

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

221  

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

226  

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

231  

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

237  

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

241  

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

247  

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

252  

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

256  

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

260  

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

265  

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

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

273  

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

278  

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

290  

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

297  

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

305  

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

314  

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

324  

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

329  

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

334  

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

340  

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

346  

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

351  

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

355  

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

359  

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

364  

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

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

372  

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

384  

363  
public:
385  
public:
364  
    run_wrapper_ex(Ex ex, Alloc alloc)
386  
    run_wrapper_ex(Ex ex, Alloc alloc)
365  
        requires InheritStopToken
387  
        requires InheritStopToken
366  
        : ex_(std::move(ex))
388  
        : ex_(std::move(ex))
367  
        , resource_(alloc)
389  
        , resource_(alloc)
368  
        , alloc_(std::move(alloc))
390  
        , alloc_(std::move(alloc))
369  
    {
391  
    {
370  
        set_current_frame_allocator(&resource_);
392  
        set_current_frame_allocator(&resource_);
371  
    }
393  
    }
372  

394  

373  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
395  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
374  
        requires (!InheritStopToken)
396  
        requires (!InheritStopToken)
375  
        : ex_(std::move(ex))
397  
        : ex_(std::move(ex))
376  
        , st_(std::move(st))
398  
        , st_(std::move(st))
377  
        , resource_(alloc)
399  
        , resource_(alloc)
378  
        , alloc_(std::move(alloc))
400  
        , alloc_(std::move(alloc))
379  
    {
401  
    {
380  
        set_current_frame_allocator(&resource_);
402  
        set_current_frame_allocator(&resource_);
381  
    }
403  
    }
382  

404  

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

410  

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

422  

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

430  

409  
public:
431  
public:
410  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
432  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
411  
        requires InheritStopToken
433  
        requires InheritStopToken
412  
        : ex_(std::move(ex))
434  
        : ex_(std::move(ex))
413  
        , mr_(mr)
435  
        , mr_(mr)
414  
    {
436  
    {
415  
        set_current_frame_allocator(mr_);
437  
        set_current_frame_allocator(mr_);
416  
    }
438  
    }
417  

439  

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

448  

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

454  

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

466  

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

473  

452  
public:
474  
public:
453  
    explicit run_wrapper_ex(Ex ex)
475  
    explicit run_wrapper_ex(Ex ex)
454  
        requires InheritStopToken
476  
        requires InheritStopToken
455  
        : ex_(std::move(ex))
477  
        : ex_(std::move(ex))
456  
    {
478  
    {
457  
    }
479  
    }
458  

480  

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

487  

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

493  

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

505  

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

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

513  

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

523  

496  
public:
524  
public:
497  
    explicit run_wrapper(Alloc alloc)
525  
    explicit run_wrapper(Alloc alloc)
498  
        requires InheritStopToken
526  
        requires InheritStopToken
499  
        : resource_(alloc)
527  
        : resource_(alloc)
500  
        , alloc_(std::move(alloc))
528  
        , alloc_(std::move(alloc))
501  
    {
529  
    {
502  
        set_current_frame_allocator(&resource_);
530  
        set_current_frame_allocator(&resource_);
503  
    }
531  
    }
504  

532  

505  
    run_wrapper(std::stop_token st, Alloc alloc)
533  
    run_wrapper(std::stop_token st, Alloc alloc)
506  
        requires (!InheritStopToken)
534  
        requires (!InheritStopToken)
507  
        : st_(std::move(st))
535  
        : st_(std::move(st))
508  
        , resource_(alloc)
536  
        , resource_(alloc)
509  
        , alloc_(std::move(alloc))
537  
        , alloc_(std::move(alloc))
510  
    {
538  
    {
511  
        set_current_frame_allocator(&resource_);
539  
        set_current_frame_allocator(&resource_);
512  
    }
540  
    }
513  

541  

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

547  

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

559  

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

566  

539  
public:
567  
public:
540  
    explicit run_wrapper(std::pmr::memory_resource* mr)
568  
    explicit run_wrapper(std::pmr::memory_resource* mr)
541  
        requires InheritStopToken
569  
        requires InheritStopToken
542  
        : mr_(mr)
570  
        : mr_(mr)
543  
    {
571  
    {
544  
        set_current_frame_allocator(mr_);
572  
        set_current_frame_allocator(mr_);
545  
    }
573  
    }
546  

574  

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

582  

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

588  

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

600  

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

606  

579  
public:
607  
public:
580  
    explicit run_wrapper(std::stop_token st)
608  
    explicit run_wrapper(std::stop_token st)
581  
        : st_(std::move(st))
609  
        : st_(std::move(st))
582  
    {
610  
    {
583  
    }
611  
    }
584  

612  

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

618  

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

625  

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

627  

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

629  

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

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

637  

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

640  

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

645  

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

647  

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

649  

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

659  

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

661  

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

664  

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

674  

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

676  

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

679  

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

689  

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

691  

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

694  

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

704  

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

706  

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

710  

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

720  

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

722  

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

726  

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

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

742  

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

744  

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

747  

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

753  

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

755  

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

763  

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

765  

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

768  

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

770  

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

778  

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

780  

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

783  

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

785  

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

794  

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

796  

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

798  

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

801  

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

810  

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

812  

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

814  

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

817  

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

827  

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

829  

790  
#endif
830  
#endif