1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_RUN_HPP
10  
#ifndef BOOST_CAPY_RUN_HPP
11  
#define BOOST_CAPY_RUN_HPP
11  
#define BOOST_CAPY_RUN_HPP
12  

12  

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

22  

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

28  

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

32  

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

34  

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

36  

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

44  

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

48  

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

53  

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

59  

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

61  

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

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

69  

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

75  

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

84  

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

90  

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

92  

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

99  

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

106  

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

111  

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

115  

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

117  

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

119  

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

124  

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

127  

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

130  

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

140  

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

145  

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

150  

 
151 +
//----------------------------------------------------------
 
152 +
//
 
153 +
// run_awaitable_ex - with executor (executor switch)
 
154 +
//
 
155 +
//----------------------------------------------------------
 
156 +

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

158  

147  
    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
148  
    co_await expression's lifetime extension keeps both alive for the
160  
    co_await expression's lifetime extension keeps both alive for the
149  
    duration of the operation.
161  
    duration of the operation.
150  

162  

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

166  

155  
    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
156  
    outlive the inner task and all awaitables in its chain. Awaitables
168  
    outlive the inner task and all awaitables in its chain. Awaitables
157  
    may store `io_env const*` without concern for dangling references.
169  
    may store `io_env const*` without concern for dangling references.
158  

170  

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

185  

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

193  

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

202  

191  
    // with allocator, inherit stop token (use template to avoid void parameter)
203  
    // with allocator, inherit stop token (use template to avoid void parameter)
192  
    template<class A>
204  
    template<class A>
193  
        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>)
194  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
206  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
195  
        : ex_(std::move(ex))
207  
        : ex_(std::move(ex))
196  
        , resource_(std::move(alloc))
208  
        , resource_(std::move(alloc))
197  
        , inner_(std::move(inner))
209  
        , inner_(std::move(inner))
198  
    {
210  
    {
199  
    }
211  
    }
200  

212  

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

223  

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

228  

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

233  

222  
    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)
223  
    {
235  
    {
224  
        tr_ = make_dispatch_trampoline();
236  
        tr_ = make_dispatch_trampoline();
225  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
237  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
226  
        tr_.h_.promise().parent_ = cont;
238  
        tr_.h_.promise().parent_ = cont;
227  

239  

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

243  

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

249  

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

254  

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

258  

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

262  

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

267  

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

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

275  

258  
    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
259  
    directly. Executors always match, so no dispatch trampoline is needed.
277  
    directly. Executors always match, so no dispatch trampoline is needed.
260  
    The inner task's `final_suspend` resumes the parent directly via
278  
    The inner task's `final_suspend` resumes the parent directly via
261  
    unconditional symmetric transfer.
279  
    unconditional symmetric transfer.
262  

280  

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

292  

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

299  

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

307  

290  
    // with allocator, inherit stop token (use template to avoid void parameter)
308  
    // with allocator, inherit stop token (use template to avoid void parameter)
291  
    template<class A>
309  
    template<class A>
292  
        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>)
293  
    run_awaitable(A alloc, Task inner)
311  
    run_awaitable(A alloc, Task inner)
294  
        : resource_(std::move(alloc))
312  
        : resource_(std::move(alloc))
295  
        , inner_(std::move(inner))
313  
        , inner_(std::move(inner))
296  
    {
314  
    {
297  
    }
315  
    }
298  

316  

299  
    // with allocator, explicit stop token (use template to avoid void parameter)
317  
    // with allocator, explicit stop token (use template to avoid void parameter)
300  
    template<class A>
318  
    template<class A>
301  
        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>)
302  
    run_awaitable(A alloc, Task inner, std::stop_token st)
320  
    run_awaitable(A alloc, Task inner, std::stop_token st)
303  
        : resource_(std::move(alloc))
321  
        : resource_(std::move(alloc))
304  
        , st_(std::move(st))
322  
        , st_(std::move(st))
305  
        , inner_(std::move(inner))
323  
        , inner_(std::move(inner))
306  
    {
324  
    {
307  
    }
325  
    }
308  

326  

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

331  

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

336  

319  
    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)
320  
    {
338  
    {
321  
        auto h = inner_.handle();
339  
        auto h = inner_.handle();
322  
        auto& p = h.promise();
340  
        auto& p = h.promise();
323  
        p.set_continuation(cont);
341  
        p.set_continuation(cont);
324  

342  

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

348  

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

353  

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

357  

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

361  

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

366  

 
367 +
//----------------------------------------------------------
 
368 +
//
 
369 +
// run_wrapper_ex - with executor
 
370 +
//
 
371 +
//----------------------------------------------------------
 
372 +

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

374  

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

386  

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

396  

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

406  

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

412  

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

424  

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

432  

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

441  

418  
    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)
419  
        requires (!InheritStopToken)
443  
        requires (!InheritStopToken)
420  
        : ex_(std::move(ex))
444  
        : ex_(std::move(ex))
421  
        , st_(std::move(st))
445  
        , st_(std::move(st))
422  
        , mr_(mr)
446  
        , mr_(mr)
423  
    {
447  
    {
424  
        set_current_frame_allocator(mr_);
448  
        set_current_frame_allocator(mr_);
425  
    }
449  
    }
426  

450  

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

456  

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

468  

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

475  

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

482  

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

489  

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

495  

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

507  

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

484  
/** 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.
485  

515  

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

525  

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

534  

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

543  

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

549  

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

561  

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

568  

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

576  

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

584  

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

590  

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

602  

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

608  

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

614  

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

620  

591  
    template<IoRunnable Task>
621  
    template<IoRunnable Task>
592  
    [[nodiscard]] auto operator()(Task t) &&
622  
    [[nodiscard]] auto operator()(Task t) &&
593  
    {
623  
    {
594  
        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_)};
595  
    }
625  
    }
596  
};
626  
};
597  

627  

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

629  

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

631  

 
632 +
//----------------------------------------------------------
 
633 +
//
 
634 +
// run() overloads - with executor
 
635 +
//
 
636 +
//----------------------------------------------------------
 
637 +

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

639  

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

642  

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

647  

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

649  

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

651  

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

661  

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

663  

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

666  

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

676  

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

678  

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

681  

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

691  

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

693  

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

696  

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

706  

671  
/** 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.
672  

708  

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

712  

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

722  

687  
/** 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.
688  

724  

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

728  

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

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

744  

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

746  

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

749  

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

755  

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

757  

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

765  

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

767  

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

770  

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

772  

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

780  

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

782  

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

785  

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

787  

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

796  

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

798  

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

800  

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

803  

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

812  

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

814  

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

816  

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

819  

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

829  

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

831  

790  
#endif
832  
#endif