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  

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

66  

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

68  

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

74  

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

83  

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

89  

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

91  

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

98  

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

104  

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

109  

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

113  

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

115  

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

117  

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

122  

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

125  

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

128  

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

138  

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

143  

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

148  

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

154  

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

156  

159  
    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
160  
    co_await expression's lifetime extension keeps both alive for the
158  
    co_await expression's lifetime extension keeps both alive for the
161  
    duration of the operation.
159  
    duration of the operation.
162  

160  

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

164  

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

168  

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

183  

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

191  

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

200  

203  
    // with allocator, inherit stop token (use template to avoid void parameter)
201  
    // with allocator, inherit stop token (use template to avoid void parameter)
204  
    template<class A>
202  
    template<class A>
205  
        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>)
206  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
204  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
207  
        : ex_(std::move(ex))
205  
        : ex_(std::move(ex))
208  
        , resource_(std::move(alloc))
206  
        , resource_(std::move(alloc))
209  
        , inner_(std::move(inner))
207  
        , inner_(std::move(inner))
210  
    {
208  
    {
211  
    }
209  
    }
212  

210  

213  
    // with allocator, explicit stop token (use template to avoid void parameter)
211  
    // with allocator, explicit stop token (use template to avoid void parameter)
214  
    template<class A>
212  
    template<class A>
215  
        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>)
216  
    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)
217  
        : ex_(std::move(ex))
215  
        : ex_(std::move(ex))
218  
        , resource_(std::move(alloc))
216  
        , resource_(std::move(alloc))
219  
        , st_(std::move(st))
217  
        , st_(std::move(st))
220  
        , inner_(std::move(inner))
218  
        , inner_(std::move(inner))
221  
    {
219  
    {
222  
    }
220  
    }
223  

221  

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

226  

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

231  

234  
    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)
235  
    {
233  
    {
236  
        tr_ = make_dispatch_trampoline();
234  
        tr_ = make_dispatch_trampoline();
237  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
235  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
238  
        tr_.h_.promise().parent_ = cont;
236  
        tr_.h_.promise().parent_ = cont;
239  

237  

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

241  

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

247  

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

252  

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

256  

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

260  

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

265  

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

271  

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

273  

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

278  

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

290  

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

297  

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

305  

308  
    // with allocator, inherit stop token (use template to avoid void parameter)
306  
    // with allocator, inherit stop token (use template to avoid void parameter)
309  
    template<class A>
307  
    template<class A>
310  
        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>)
311  
    run_awaitable(A alloc, Task inner)
309  
    run_awaitable(A alloc, Task inner)
312  
        : resource_(std::move(alloc))
310  
        : resource_(std::move(alloc))
313  
        , inner_(std::move(inner))
311  
        , inner_(std::move(inner))
314  
    {
312  
    {
315  
    }
313  
    }
316  

314  

317  
    // with allocator, explicit stop token (use template to avoid void parameter)
315  
    // with allocator, explicit stop token (use template to avoid void parameter)
318  
    template<class A>
316  
    template<class A>
319  
        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>)
320  
    run_awaitable(A alloc, Task inner, std::stop_token st)
318  
    run_awaitable(A alloc, Task inner, std::stop_token st)
321  
        : resource_(std::move(alloc))
319  
        : resource_(std::move(alloc))
322  
        , st_(std::move(st))
320  
        , st_(std::move(st))
323  
        , inner_(std::move(inner))
321  
        , inner_(std::move(inner))
324  
    {
322  
    {
325  
    }
323  
    }
326  

324  

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

329  

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

334  

337  
    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)
338  
    {
336  
    {
339  
        auto h = inner_.handle();
337  
        auto h = inner_.handle();
340  
        auto& p = h.promise();
338  
        auto& p = h.promise();
341  
        p.set_continuation(cont);
339  
        p.set_continuation(cont);
342  

340  

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

346  

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

351  

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

355  

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

359  

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

364  

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

370  

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

372  

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

384  

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

394  

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

404  

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

410  

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

422  

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

430  

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

439  

442  
    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)
443  
        requires (!InheritStopToken)
441  
        requires (!InheritStopToken)
444  
        : ex_(std::move(ex))
442  
        : ex_(std::move(ex))
445  
        , st_(std::move(st))
443  
        , st_(std::move(st))
446  
        , mr_(mr)
444  
        , mr_(mr)
447  
    {
445  
    {
448  
        set_current_frame_allocator(mr_);
446  
        set_current_frame_allocator(mr_);
449  
    }
447  
    }
450  

448  

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

454  

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

466  

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

473  

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

480  

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

487  

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

493  

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

505  

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

511  

514  
/** 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.
515  

513  

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

523  

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

532  

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

541  

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

547  

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

559  

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

566  

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

574  

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

582  

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

588  

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

600  

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

606  

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

612  

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

618  

621  
    template<IoRunnable Task>
619  
    template<IoRunnable Task>
622  
    [[nodiscard]] auto operator()(Task t) &&
620  
    [[nodiscard]] auto operator()(Task t) &&
623  
    {
621  
    {
624  
        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_)};
625  
    }
623  
    }
626  
};
624  
};
627  

625  

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

627  

630  
namespace boost::capy {
628  
namespace boost::capy {
631  

629  

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

635  

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

637  

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

640  

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

645  

648  
    @param ex The executor on which the task should run.
646  
    @param ex The executor on which the task should run.
649  

647  

650  
    @return A wrapper that accepts a task for execution.
648  
    @return A wrapper that accepts a task for execution.
651  

649  

652  
    @see task
650  
    @see task
653  
    @see executor
651  
    @see executor
654  
*/
652  
*/
655  
template<Executor Ex>
653  
template<Executor Ex>
656  
[[nodiscard]] auto
654  
[[nodiscard]] auto
657  
run(Ex ex)
655  
run(Ex ex)
658  
{
656  
{
659  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
657  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
660  
}
658  
}
661  

659  

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

661  

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

664  

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

674  

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

676  

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

679  

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

689  

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

691  

694  
    @param ex The executor on which the task should run.
692  
    @param ex The executor on which the task should run.
695  
    @param alloc The allocator for frame allocation.
693  
    @param alloc The allocator for frame allocation.
696  

694  

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

704  

707  
/** 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.
708  

706  

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

710  

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

720  

723  
/** 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.
724  

722  

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

726  

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

736  

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

742  

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

744  

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

747  

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

753  

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

755  

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

763  

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

765  

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

768  

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

770  

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

778  

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

780  

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

783  

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

785  

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

794  

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

796  

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

798  

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

801  

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

810  

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

812  

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

814  

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

817  

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

827  

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

829  

832  
#endif
830  
#endif