1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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_ASYNC_HPP
10  
#ifndef BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/run.hpp>
14  
#include <boost/capy/detail/run.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
16  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/io_launchable_task.hpp>
17  
#include <boost/capy/concept/io_launchable_task.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/ex/recycling_memory_resource.hpp>
20  
#include <boost/capy/ex/recycling_memory_resource.hpp>
21  

21  

22  
#include <coroutine>
22  
#include <coroutine>
23  
#include <memory_resource>
23  
#include <memory_resource>
24  
#include <new>
24  
#include <new>
25  
#include <stop_token>
25  
#include <stop_token>
26  
#include <type_traits>
26  
#include <type_traits>
27  

27  

28  
namespace boost {
28  
namespace boost {
29  
namespace capy {
29  
namespace capy {
30  
namespace detail {
30  
namespace detail {
31  

31  

32  
/// Function pointer type for type-erased frame deallocation.
32  
/// Function pointer type for type-erased frame deallocation.
33  
using dealloc_fn = void(*)(void*, std::size_t);
33  
using dealloc_fn = void(*)(void*, std::size_t);
34  

34  

35  
/// Type-erased deallocator implementation for trampoline frames.
35  
/// Type-erased deallocator implementation for trampoline frames.
36  
template<class Alloc>
36  
template<class Alloc>
37  
void dealloc_impl(void* raw, std::size_t total)
37  
void dealloc_impl(void* raw, std::size_t total)
38  
{
38  
{
39  
    static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
39  
    static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
40  
    auto* a = std::launder(reinterpret_cast<Alloc*>(
40  
    auto* a = std::launder(reinterpret_cast<Alloc*>(
41  
        static_cast<char*>(raw) + total - sizeof(Alloc)));
41  
        static_cast<char*>(raw) + total - sizeof(Alloc)));
42  
    Alloc ba(std::move(*a));
42  
    Alloc ba(std::move(*a));
43  
    a->~Alloc();
43  
    a->~Alloc();
44  
    ba.deallocate(static_cast<std::byte*>(raw), total);
44  
    ba.deallocate(static_cast<std::byte*>(raw), total);
45  
}
45  
}
46  

46  

47  
/// Awaiter to access the promise from within the coroutine.
47  
/// Awaiter to access the promise from within the coroutine.
48  
template<class Promise>
48  
template<class Promise>
49  
struct get_promise_awaiter
49  
struct get_promise_awaiter
50  
{
50  
{
51  
    Promise* p_ = nullptr;
51  
    Promise* p_ = nullptr;
52  

52  

53  
    bool await_ready() const noexcept { return false; }
53  
    bool await_ready() const noexcept { return false; }
54  

54  

55  
    bool await_suspend(std::coroutine_handle<Promise> h) noexcept
55  
    bool await_suspend(std::coroutine_handle<Promise> h) noexcept
56  
    {
56  
    {
57  
        p_ = &h.promise();
57  
        p_ = &h.promise();
58  
        return false;
58  
        return false;
59  
    }
59  
    }
60  

60  

61  
    Promise& await_resume() const noexcept
61  
    Promise& await_resume() const noexcept
62  
    {
62  
    {
63  
        return *p_;
63  
        return *p_;
64  
    }
64  
    }
65  
};
65  
};
66  

66  

67  
/** Internal run_async_trampoline coroutine for run_async.
67  
/** Internal run_async_trampoline coroutine for run_async.
68  

68  

69  
    The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
69  
    The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
70  
    order) and serves as the task's continuation. When the task final_suspends,
70  
    order) and serves as the task's continuation. When the task final_suspends,
71  
    control returns to the run_async_trampoline which then invokes the appropriate handler.
71  
    control returns to the run_async_trampoline which then invokes the appropriate handler.
72  

72  

73  
    For value-type allocators, the run_async_trampoline stores a frame_memory_resource
73  
    For value-type allocators, the run_async_trampoline stores a frame_memory_resource
74  
    that wraps the allocator. For memory_resource*, it stores the pointer directly.
74  
    that wraps the allocator. For memory_resource*, it stores the pointer directly.
75  

75  

76  
    @tparam Ex The executor type.
76  
    @tparam Ex The executor type.
77  
    @tparam Handlers The handler type (default_handler or handler_pair).
77  
    @tparam Handlers The handler type (default_handler or handler_pair).
78  
    @tparam Alloc The allocator type (value type or memory_resource*).
78  
    @tparam Alloc The allocator type (value type or memory_resource*).
79  
*/
79  
*/
80  
template<class Ex, class Handlers, class Alloc>
80  
template<class Ex, class Handlers, class Alloc>
81  
struct run_async_trampoline
81  
struct run_async_trampoline
82  
{
82  
{
83  
    using invoke_fn = void(*)(void*, Handlers&);
83  
    using invoke_fn = void(*)(void*, Handlers&);
84  

84  

85  
    struct promise_type
85  
    struct promise_type
86  
    {
86  
    {
87  
        Ex ex_;
87  
        Ex ex_;
88  
        Handlers handlers_;
88  
        Handlers handlers_;
89  
        frame_memory_resource<Alloc> resource_;
89  
        frame_memory_resource<Alloc> resource_;
90  
        invoke_fn invoke_ = nullptr;
90  
        invoke_fn invoke_ = nullptr;
91  
        void* task_promise_ = nullptr;
91  
        void* task_promise_ = nullptr;
92  
        std::coroutine_handle<> task_h_;
92  
        std::coroutine_handle<> task_h_;
93  

93  

94  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
94  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
95  
            : ex_(std::move(ex))
95  
            : ex_(std::move(ex))
96  
            , handlers_(std::move(h))
96  
            , handlers_(std::move(h))
97  
            , resource_(std::move(a))
97  
            , resource_(std::move(a))
98  
        {
98  
        {
99  
        }
99  
        }
100  

100  

101  
        static void* operator new(
101  
        static void* operator new(
102  
            std::size_t size, Ex const&, Handlers const&, Alloc a)
102  
            std::size_t size, Ex const&, Handlers const&, Alloc a)
103  
        {
103  
        {
104  
            using byte_alloc = typename std::allocator_traits<Alloc>
104  
            using byte_alloc = typename std::allocator_traits<Alloc>
105  
                ::template rebind_alloc<std::byte>;
105  
                ::template rebind_alloc<std::byte>;
106  

106  

107  
            constexpr auto footer_align =
107  
            constexpr auto footer_align =
108  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
108  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
109  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
109  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
110  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
110  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
111  

111  

112  
            byte_alloc ba(std::move(a));
112  
            byte_alloc ba(std::move(a));
113  
            void* raw = ba.allocate(total);
113  
            void* raw = ba.allocate(total);
114  

114  

115  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
115  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
116  
                static_cast<char*>(raw) + padded);
116  
                static_cast<char*>(raw) + padded);
117  
            *fn_loc = &dealloc_impl<byte_alloc>;
117  
            *fn_loc = &dealloc_impl<byte_alloc>;
118  

118  

119  
            new (fn_loc + 1) byte_alloc(std::move(ba));
119  
            new (fn_loc + 1) byte_alloc(std::move(ba));
120  

120  

121  
            return raw;
121  
            return raw;
122  
        }
122  
        }
123  

123  

124  
        static void operator delete(void* ptr, std::size_t size)
124  
        static void operator delete(void* ptr, std::size_t size)
125  
        {
125  
        {
126  
            constexpr auto footer_align =
126  
            constexpr auto footer_align =
127  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
127  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
128  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
128  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
129  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
129  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
130  

130  

131  
            auto* fn = reinterpret_cast<dealloc_fn*>(
131  
            auto* fn = reinterpret_cast<dealloc_fn*>(
132  
                static_cast<char*>(ptr) + padded);
132  
                static_cast<char*>(ptr) + padded);
133  
            (*fn)(ptr, total);
133  
            (*fn)(ptr, total);
134  
        }
134  
        }
135  

135  

136  
        std::pmr::memory_resource* get_resource() noexcept
136  
        std::pmr::memory_resource* get_resource() noexcept
137  
        {
137  
        {
138  
            return &resource_;
138  
            return &resource_;
139  
        }
139  
        }
140  

140  

141  
        run_async_trampoline get_return_object() noexcept
141  
        run_async_trampoline get_return_object() noexcept
142  
        {
142  
        {
143  
            return run_async_trampoline{
143  
            return run_async_trampoline{
144  
                std::coroutine_handle<promise_type>::from_promise(*this)};
144  
                std::coroutine_handle<promise_type>::from_promise(*this)};
145  
        }
145  
        }
146  

146  

147  
        std::suspend_always initial_suspend() noexcept
147  
        std::suspend_always initial_suspend() noexcept
148  
        {
148  
        {
149  
            return {};
149  
            return {};
150  
        }
150  
        }
151  

151  

152  
        std::suspend_never final_suspend() noexcept
152  
        std::suspend_never final_suspend() noexcept
153  
        {
153  
        {
154  
            return {};
154  
            return {};
155  
        }
155  
        }
156  

156  

157  
        void return_void() noexcept
157  
        void return_void() noexcept
158  
        {
158  
        {
159  
        }
159  
        }
160  

160  

161  
        void unhandled_exception() noexcept
161  
        void unhandled_exception() noexcept
162  
        {
162  
        {
163  
        }
163  
        }
164  
    };
164  
    };
165  

165  

166  
    std::coroutine_handle<promise_type> h_;
166  
    std::coroutine_handle<promise_type> h_;
167  

167  

168  
    template<IoLaunchableTask Task>
168  
    template<IoLaunchableTask Task>
169  
    static void invoke_impl(void* p, Handlers& h)
169  
    static void invoke_impl(void* p, Handlers& h)
170  
    {
170  
    {
171  
        using R = decltype(std::declval<Task&>().await_resume());
171  
        using R = decltype(std::declval<Task&>().await_resume());
172  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
172  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
173  
        if(promise.exception())
173  
        if(promise.exception())
174  
            h(promise.exception());
174  
            h(promise.exception());
175  
        else if constexpr(std::is_void_v<R>)
175  
        else if constexpr(std::is_void_v<R>)
176  
            h();
176  
            h();
177  
        else
177  
        else
178  
            h(std::move(promise.result()));
178  
            h(std::move(promise.result()));
179  
    }
179  
    }
180  
};
180  
};
181  

181  

182  
/** Specialization for memory_resource* - stores pointer directly.
182  
/** Specialization for memory_resource* - stores pointer directly.
183  

183  

184  
    This avoids double indirection when the user passes a memory_resource*.
184  
    This avoids double indirection when the user passes a memory_resource*.
185  
*/
185  
*/
186  
template<class Ex, class Handlers>
186  
template<class Ex, class Handlers>
187  
struct run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
187  
struct run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
188  
{
188  
{
189  
    using invoke_fn = void(*)(void*, Handlers&);
189  
    using invoke_fn = void(*)(void*, Handlers&);
190  

190  

191  
    struct promise_type
191  
    struct promise_type
192  
    {
192  
    {
193  
        Ex ex_;
193  
        Ex ex_;
194  
        Handlers handlers_;
194  
        Handlers handlers_;
195  
        std::pmr::memory_resource* mr_;
195  
        std::pmr::memory_resource* mr_;
196  
        invoke_fn invoke_ = nullptr;
196  
        invoke_fn invoke_ = nullptr;
197  
        void* task_promise_ = nullptr;
197  
        void* task_promise_ = nullptr;
198  
        std::coroutine_handle<> task_h_;
198  
        std::coroutine_handle<> task_h_;
199  

199  

200  
        promise_type(
200  
        promise_type(
201  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
201  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
202  
            : ex_(std::move(ex))
202  
            : ex_(std::move(ex))
203  
            , handlers_(std::move(h))
203  
            , handlers_(std::move(h))
204  
            , mr_(mr)
204  
            , mr_(mr)
205  
        {
205  
        {
206  
        }
206  
        }
207  

207  

208  
        static void* operator new(
208  
        static void* operator new(
209  
            std::size_t size, Ex const&, Handlers const&,
209  
            std::size_t size, Ex const&, Handlers const&,
210  
            std::pmr::memory_resource* mr)
210  
            std::pmr::memory_resource* mr)
211  
        {
211  
        {
212  
            auto total = size + sizeof(mr);
212  
            auto total = size + sizeof(mr);
213  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
213  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
214  
            *reinterpret_cast<std::pmr::memory_resource**>(
214  
            *reinterpret_cast<std::pmr::memory_resource**>(
215  
                static_cast<char*>(raw) + size) = mr;
215  
                static_cast<char*>(raw) + size) = mr;
216  
            return raw;
216  
            return raw;
217  
        }
217  
        }
218  

218  

219  
        static void operator delete(void* ptr, std::size_t size)
219  
        static void operator delete(void* ptr, std::size_t size)
220  
        {
220  
        {
221  
            auto* mr = *reinterpret_cast<std::pmr::memory_resource**>(
221  
            auto* mr = *reinterpret_cast<std::pmr::memory_resource**>(
222  
                static_cast<char*>(ptr) + size);
222  
                static_cast<char*>(ptr) + size);
223  
            mr->deallocate(ptr, size + sizeof(mr), alignof(std::max_align_t));
223  
            mr->deallocate(ptr, size + sizeof(mr), alignof(std::max_align_t));
224  
        }
224  
        }
225  

225  

226  
        std::pmr::memory_resource* get_resource() noexcept
226  
        std::pmr::memory_resource* get_resource() noexcept
227  
        {
227  
        {
228  
            return mr_;
228  
            return mr_;
229  
        }
229  
        }
230  

230  

231  
        run_async_trampoline get_return_object() noexcept
231  
        run_async_trampoline get_return_object() noexcept
232  
        {
232  
        {
233  
            return run_async_trampoline{
233  
            return run_async_trampoline{
234  
                std::coroutine_handle<promise_type>::from_promise(*this)};
234  
                std::coroutine_handle<promise_type>::from_promise(*this)};
235  
        }
235  
        }
236  

236  

237  
        std::suspend_always initial_suspend() noexcept
237  
        std::suspend_always initial_suspend() noexcept
238  
        {
238  
        {
239  
            return {};
239  
            return {};
240  
        }
240  
        }
241  

241  

242  
        std::suspend_never final_suspend() noexcept
242  
        std::suspend_never final_suspend() noexcept
243  
        {
243  
        {
244  
            return {};
244  
            return {};
245  
        }
245  
        }
246  

246  

247  
        void return_void() noexcept
247  
        void return_void() noexcept
248  
        {
248  
        {
249  
        }
249  
        }
250  

250  

251  
        void unhandled_exception() noexcept
251  
        void unhandled_exception() noexcept
252  
        {
252  
        {
253  
        }
253  
        }
254  
    };
254  
    };
255  

255  

256  
    std::coroutine_handle<promise_type> h_;
256  
    std::coroutine_handle<promise_type> h_;
257  

257  

258  
    template<IoLaunchableTask Task>
258  
    template<IoLaunchableTask Task>
259  
    static void invoke_impl(void* p, Handlers& h)
259  
    static void invoke_impl(void* p, Handlers& h)
260  
    {
260  
    {
261  
        using R = decltype(std::declval<Task&>().await_resume());
261  
        using R = decltype(std::declval<Task&>().await_resume());
262  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
262  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
263  
        if(promise.exception())
263  
        if(promise.exception())
264  
            h(promise.exception());
264  
            h(promise.exception());
265  
        else if constexpr(std::is_void_v<R>)
265  
        else if constexpr(std::is_void_v<R>)
266  
            h();
266  
            h();
267  
        else
267  
        else
268  
            h(std::move(promise.result()));
268  
            h(std::move(promise.result()));
269  
    }
269  
    }
270  
};
270  
};
271  

271  

272  
/// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
272  
/// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
273  
template<class Ex, class Handlers, class Alloc>
273  
template<class Ex, class Handlers, class Alloc>
274  
run_async_trampoline<Ex, Handlers, Alloc>
274  
run_async_trampoline<Ex, Handlers, Alloc>
275  
make_trampoline(Ex, Handlers, Alloc)
275  
make_trampoline(Ex, Handlers, Alloc)
276  
{
276  
{
277  
    // promise_type ctor steals the parameters
277  
    // promise_type ctor steals the parameters
278  
    auto& p = co_await get_promise_awaiter<
278  
    auto& p = co_await get_promise_awaiter<
279  
        typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
279  
        typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
280  
    
280  
    
281  
    p.invoke_(p.task_promise_, p.handlers_);
281  
    p.invoke_(p.task_promise_, p.handlers_);
282  
    p.task_h_.destroy();
282  
    p.task_h_.destroy();
283  
}
283  
}
284  

284  

285  
} // namespace detail
285  
} // namespace detail
286  

286  

287  
//----------------------------------------------------------
287  
//----------------------------------------------------------
288  
//
288  
//
289  
// run_async_wrapper
289  
// run_async_wrapper
290  
//
290  
//
291  
//----------------------------------------------------------
291  
//----------------------------------------------------------
292  

292  

293  
/** Wrapper returned by run_async that accepts a task for execution.
293  
/** Wrapper returned by run_async that accepts a task for execution.
294  

294  

295  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
295  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
296  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
296  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
297  
    (before the task due to C++17 postfix evaluation order).
297  
    (before the task due to C++17 postfix evaluation order).
298  

298  

299  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
299  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
300  
    be used as a temporary, preventing misuse that would violate LIFO ordering.
300  
    be used as a temporary, preventing misuse that would violate LIFO ordering.
301  

301  

302  
    @tparam Ex The executor type satisfying the `Executor` concept.
302  
    @tparam Ex The executor type satisfying the `Executor` concept.
303  
    @tparam Handlers The handler type (default_handler or handler_pair).
303  
    @tparam Handlers The handler type (default_handler or handler_pair).
304  
    @tparam Alloc The allocator type (value type or memory_resource*).
304  
    @tparam Alloc The allocator type (value type or memory_resource*).
305  

305  

306  
    @par Thread Safety
306  
    @par Thread Safety
307  
    The wrapper itself should only be used from one thread. The handlers
307  
    The wrapper itself should only be used from one thread. The handlers
308  
    may be invoked from any thread where the executor schedules work.
308  
    may be invoked from any thread where the executor schedules work.
309  

309  

310  
    @par Example
310  
    @par Example
311  
    @code
311  
    @code
312  
    // Correct usage - wrapper is temporary
312  
    // Correct usage - wrapper is temporary
313  
    run_async(ex)(my_task());
313  
    run_async(ex)(my_task());
314  

314  

315  
    // Compile error - cannot call operator() on lvalue
315  
    // Compile error - cannot call operator() on lvalue
316  
    auto w = run_async(ex);
316  
    auto w = run_async(ex);
317  
    w(my_task());  // Error: operator() requires rvalue
317  
    w(my_task());  // Error: operator() requires rvalue
318  
    @endcode
318  
    @endcode
319  

319  

320  
    @see run_async
320  
    @see run_async
321  
*/
321  
*/
322  
template<Executor Ex, class Handlers, class Alloc>
322  
template<Executor Ex, class Handlers, class Alloc>
323  
class [[nodiscard]] run_async_wrapper
323  
class [[nodiscard]] run_async_wrapper
324  
{
324  
{
325  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
325  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
326  
    std::stop_token st_;
326  
    std::stop_token st_;
327  

327  

328  
public:
328  
public:
329  
    /// Construct wrapper with executor, stop token, handlers, and allocator.
329  
    /// Construct wrapper with executor, stop token, handlers, and allocator.
330  
    run_async_wrapper(
330  
    run_async_wrapper(
331  
        Ex ex,
331  
        Ex ex,
332  
        std::stop_token st,
332  
        std::stop_token st,
333  
        Handlers h,
333  
        Handlers h,
334  
        Alloc a) noexcept
334  
        Alloc a) noexcept
335  
        : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
335  
        : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
336  
            std::move(ex), std::move(h), std::move(a)))
336  
            std::move(ex), std::move(h), std::move(a)))
337  
        , st_(std::move(st))
337  
        , st_(std::move(st))
338  
    {
338  
    {
339  
        if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
339  
        if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
340  
        {
340  
        {
341  
            static_assert(
341  
            static_assert(
342  
                std::is_nothrow_move_constructible_v<Alloc>,
342  
                std::is_nothrow_move_constructible_v<Alloc>,
343  
                "Allocator must be nothrow move constructible");
343  
                "Allocator must be nothrow move constructible");
344  
        }
344  
        }
345  
        // Set TLS before task argument is evaluated
345  
        // Set TLS before task argument is evaluated
346  
        current_frame_allocator() = tr_.h_.promise().get_resource();
346  
        current_frame_allocator() = tr_.h_.promise().get_resource();
347  
    }
347  
    }
348  

348  

349  
    // Non-copyable, non-movable (must be used immediately)
349  
    // Non-copyable, non-movable (must be used immediately)
350  
    run_async_wrapper(run_async_wrapper const&) = delete;
350  
    run_async_wrapper(run_async_wrapper const&) = delete;
351  
    run_async_wrapper(run_async_wrapper&&) = delete;
351  
    run_async_wrapper(run_async_wrapper&&) = delete;
352  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
352  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
353  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
353  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
354  

354  

355  
    /** Launch the task for execution.
355  
    /** Launch the task for execution.
356  

356  

357  
        This operator accepts a task and launches it on the executor.
357  
        This operator accepts a task and launches it on the executor.
358  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
358  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
359  
        correct LIFO destruction order.
359  
        correct LIFO destruction order.
360  

360  

361  
        @tparam Task The IoLaunchableTask type.
361  
        @tparam Task The IoLaunchableTask type.
362  

362  

363  
        @param t The task to execute. Ownership is transferred to the
363  
        @param t The task to execute. Ownership is transferred to the
364  
                 run_async_trampoline which will destroy it after completion.
364  
                 run_async_trampoline which will destroy it after completion.
365  
    */
365  
    */
366  
    template<IoLaunchableTask Task>
366  
    template<IoLaunchableTask Task>
367  
    void operator()(Task t) &&
367  
    void operator()(Task t) &&
368  
    {
368  
    {
369  
        auto task_h = t.handle();
369  
        auto task_h = t.handle();
370  
        auto& task_promise = task_h.promise();
370  
        auto& task_promise = task_h.promise();
371  
        t.release();
371  
        t.release();
372  

372  

373  
        auto& p = tr_.h_.promise();
373  
        auto& p = tr_.h_.promise();
374  

374  

375  
        // Inject Task-specific invoke function
375  
        // Inject Task-specific invoke function
376  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
376  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
377  
        p.task_promise_ = &task_promise;
377  
        p.task_promise_ = &task_promise;
378  
        p.task_h_ = task_h;
378  
        p.task_h_ = task_h;
379  

379  

380  
        // Setup task's continuation to return to run_async_trampoline
380  
        // Setup task's continuation to return to run_async_trampoline
381  
        task_promise.set_continuation(tr_.h_, p.ex_);
381  
        task_promise.set_continuation(tr_.h_, p.ex_);
382  
        task_promise.set_executor(p.ex_);
382  
        task_promise.set_executor(p.ex_);
383  
        task_promise.set_stop_token(st_);
383  
        task_promise.set_stop_token(st_);
384  

384  

385  
        // Resume task through executor
385  
        // Resume task through executor
386  
        p.ex_.dispatch(task_h);
386  
        p.ex_.dispatch(task_h);
387  
    }
387  
    }
388  
};
388  
};
389  

389  

390  
//----------------------------------------------------------
390  
//----------------------------------------------------------
391  
//
391  
//
392  
// run_async Overloads
392  
// run_async Overloads
393  
//
393  
//
394  
//----------------------------------------------------------
394  
//----------------------------------------------------------
395  

395  

396  
// Executor only (uses default recycling allocator)
396  
// Executor only (uses default recycling allocator)
397  

397  

398  
/** Asynchronously launch a lazy task on the given executor.
398  
/** Asynchronously launch a lazy task on the given executor.
399  

399  

400  
    Use this to start execution of a `task<T>` that was created lazily.
400  
    Use this to start execution of a `task<T>` that was created lazily.
401  
    The returned wrapper must be immediately invoked with the task;
401  
    The returned wrapper must be immediately invoked with the task;
402  
    storing the wrapper and calling it later violates LIFO ordering.
402  
    storing the wrapper and calling it later violates LIFO ordering.
403  

403  

404  
    Uses the default recycling frame allocator for coroutine frames.
404  
    Uses the default recycling frame allocator for coroutine frames.
405  
    With no handlers, the result is discarded and exceptions are rethrown.
405  
    With no handlers, the result is discarded and exceptions are rethrown.
406  

406  

407  
    @par Thread Safety
407  
    @par Thread Safety
408  
    The wrapper and handlers may be called from any thread where the
408  
    The wrapper and handlers may be called from any thread where the
409  
    executor schedules work.
409  
    executor schedules work.
410  

410  

411  
    @par Example
411  
    @par Example
412  
    @code
412  
    @code
413  
    run_async(ioc.get_executor())(my_task());
413  
    run_async(ioc.get_executor())(my_task());
414  
    @endcode
414  
    @endcode
415  

415  

416  
    @param ex The executor to execute the task on.
416  
    @param ex The executor to execute the task on.
417  

417  

418  
    @return A wrapper that accepts a `task<T>` for immediate execution.
418  
    @return A wrapper that accepts a `task<T>` for immediate execution.
419  

419  

420  
    @see task
420  
    @see task
421  
    @see executor
421  
    @see executor
422  
*/
422  
*/
423  
template<Executor Ex>
423  
template<Executor Ex>
424  
[[nodiscard]] auto
424  
[[nodiscard]] auto
425  
run_async(Ex ex)
425  
run_async(Ex ex)
426  
{
426  
{
427  
    auto* mr = ex.context().get_frame_allocator();
427  
    auto* mr = ex.context().get_frame_allocator();
428  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
428  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
429  
        std::move(ex),
429  
        std::move(ex),
430  
        std::stop_token{},
430  
        std::stop_token{},
431  
        detail::default_handler{},
431  
        detail::default_handler{},
432  
        mr);
432  
        mr);
433  
}
433  
}
434  

434  

435  
/** Asynchronously launch a lazy task with a result handler.
435  
/** Asynchronously launch a lazy task with a result handler.
436  

436  

437  
    The handler `h1` is called with the task's result on success. If `h1`
437  
    The handler `h1` is called with the task's result on success. If `h1`
438  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
438  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
439  
    Otherwise, exceptions are rethrown.
439  
    Otherwise, exceptions are rethrown.
440  

440  

441  
    @par Thread Safety
441  
    @par Thread Safety
442  
    The handler may be called from any thread where the executor
442  
    The handler may be called from any thread where the executor
443  
    schedules work.
443  
    schedules work.
444  

444  

445  
    @par Example
445  
    @par Example
446  
    @code
446  
    @code
447  
    // Handler for result only (exceptions rethrown)
447  
    // Handler for result only (exceptions rethrown)
448  
    run_async(ex, [](int result) {
448  
    run_async(ex, [](int result) {
449  
        std::cout << "Got: " << result << "\n";
449  
        std::cout << "Got: " << result << "\n";
450  
    })(compute_value());
450  
    })(compute_value());
451  

451  

452  
    // Overloaded handler for both result and exception
452  
    // Overloaded handler for both result and exception
453  
    run_async(ex, overloaded{
453  
    run_async(ex, overloaded{
454  
        [](int result) { std::cout << "Got: " << result << "\n"; },
454  
        [](int result) { std::cout << "Got: " << result << "\n"; },
455  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
455  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
456  
    })(compute_value());
456  
    })(compute_value());
457  
    @endcode
457  
    @endcode
458  

458  

459  
    @param ex The executor to execute the task on.
459  
    @param ex The executor to execute the task on.
460  
    @param h1 The handler to invoke with the result (and optionally exception).
460  
    @param h1 The handler to invoke with the result (and optionally exception).
461  

461  

462  
    @return A wrapper that accepts a `task<T>` for immediate execution.
462  
    @return A wrapper that accepts a `task<T>` for immediate execution.
463  

463  

464  
    @see task
464  
    @see task
465  
    @see executor
465  
    @see executor
466  
*/
466  
*/
467  
template<Executor Ex, class H1>
467  
template<Executor Ex, class H1>
468  
[[nodiscard]] auto
468  
[[nodiscard]] auto
469  
run_async(Ex ex, H1 h1)
469  
run_async(Ex ex, H1 h1)
470  
{
470  
{
471  
    auto* mr = ex.context().get_frame_allocator();
471  
    auto* mr = ex.context().get_frame_allocator();
472  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
472  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
473  
        std::move(ex),
473  
        std::move(ex),
474  
        std::stop_token{},
474  
        std::stop_token{},
475  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
475  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
476  
        mr);
476  
        mr);
477  
}
477  
}
478  

478  

479  
/** Asynchronously launch a lazy task with separate result and error handlers.
479  
/** Asynchronously launch a lazy task with separate result and error handlers.
480  

480  

481  
    The handler `h1` is called with the task's result on success.
481  
    The handler `h1` is called with the task's result on success.
482  
    The handler `h2` is called with the exception_ptr on failure.
482  
    The handler `h2` is called with the exception_ptr on failure.
483  

483  

484  
    @par Thread Safety
484  
    @par Thread Safety
485  
    The handlers may be called from any thread where the executor
485  
    The handlers may be called from any thread where the executor
486  
    schedules work.
486  
    schedules work.
487  

487  

488  
    @par Example
488  
    @par Example
489  
    @code
489  
    @code
490  
    run_async(ex,
490  
    run_async(ex,
491  
        [](int result) { std::cout << "Got: " << result << "\n"; },
491  
        [](int result) { std::cout << "Got: " << result << "\n"; },
492  
        [](std::exception_ptr ep) {
492  
        [](std::exception_ptr ep) {
493  
            try { std::rethrow_exception(ep); }
493  
            try { std::rethrow_exception(ep); }
494  
            catch (std::exception const& e) {
494  
            catch (std::exception const& e) {
495  
                std::cout << "Error: " << e.what() << "\n";
495  
                std::cout << "Error: " << e.what() << "\n";
496  
            }
496  
            }
497  
        }
497  
        }
498  
    )(compute_value());
498  
    )(compute_value());
499  
    @endcode
499  
    @endcode
500  

500  

501  
    @param ex The executor to execute the task on.
501  
    @param ex The executor to execute the task on.
502  
    @param h1 The handler to invoke with the result on success.
502  
    @param h1 The handler to invoke with the result on success.
503  
    @param h2 The handler to invoke with the exception on failure.
503  
    @param h2 The handler to invoke with the exception on failure.
504  

504  

505  
    @return A wrapper that accepts a `task<T>` for immediate execution.
505  
    @return A wrapper that accepts a `task<T>` for immediate execution.
506  

506  

507  
    @see task
507  
    @see task
508  
    @see executor
508  
    @see executor
509  
*/
509  
*/
510  
template<Executor Ex, class H1, class H2>
510  
template<Executor Ex, class H1, class H2>
511  
[[nodiscard]] auto
511  
[[nodiscard]] auto
512  
run_async(Ex ex, H1 h1, H2 h2)
512  
run_async(Ex ex, H1 h1, H2 h2)
513  
{
513  
{
514  
    auto* mr = ex.context().get_frame_allocator();
514  
    auto* mr = ex.context().get_frame_allocator();
515  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
515  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
516  
        std::move(ex),
516  
        std::move(ex),
517  
        std::stop_token{},
517  
        std::stop_token{},
518  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
518  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
519  
        mr);
519  
        mr);
520  
}
520  
}
521  

521  

522  
// Ex + stop_token
522  
// Ex + stop_token
523  

523  

524  
/** Asynchronously launch a lazy task with stop token support.
524  
/** Asynchronously launch a lazy task with stop token support.
525  

525  

526  
    The stop token is propagated to the task, enabling cooperative
526  
    The stop token is propagated to the task, enabling cooperative
527  
    cancellation. With no handlers, the result is discarded and
527  
    cancellation. With no handlers, the result is discarded and
528  
    exceptions are rethrown.
528  
    exceptions are rethrown.
529  

529  

530  
    @par Thread Safety
530  
    @par Thread Safety
531  
    The wrapper may be called from any thread where the executor
531  
    The wrapper may be called from any thread where the executor
532  
    schedules work.
532  
    schedules work.
533  

533  

534  
    @par Example
534  
    @par Example
535  
    @code
535  
    @code
536  
    std::stop_source source;
536  
    std::stop_source source;
537  
    run_async(ex, source.get_token())(cancellable_task());
537  
    run_async(ex, source.get_token())(cancellable_task());
538  
    // Later: source.request_stop();
538  
    // Later: source.request_stop();
539  
    @endcode
539  
    @endcode
540  

540  

541  
    @param ex The executor to execute the task on.
541  
    @param ex The executor to execute the task on.
542  
    @param st The stop token for cooperative cancellation.
542  
    @param st The stop token for cooperative cancellation.
543  

543  

544  
    @return A wrapper that accepts a `task<T>` for immediate execution.
544  
    @return A wrapper that accepts a `task<T>` for immediate execution.
545  

545  

546  
    @see task
546  
    @see task
547  
    @see executor
547  
    @see executor
548  
*/
548  
*/
549  
template<Executor Ex>
549  
template<Executor Ex>
550  
[[nodiscard]] auto
550  
[[nodiscard]] auto
551  
run_async(Ex ex, std::stop_token st)
551  
run_async(Ex ex, std::stop_token st)
552  
{
552  
{
553  
    auto* mr = ex.context().get_frame_allocator();
553  
    auto* mr = ex.context().get_frame_allocator();
554  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
554  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
555  
        std::move(ex),
555  
        std::move(ex),
556  
        std::move(st),
556  
        std::move(st),
557  
        detail::default_handler{},
557  
        detail::default_handler{},
558  
        mr);
558  
        mr);
559  
}
559  
}
560  

560  

561  
/** Asynchronously launch a lazy task with stop token and result handler.
561  
/** Asynchronously launch a lazy task with stop token and result handler.
562  

562  

563  
    The stop token is propagated to the task for cooperative cancellation.
563  
    The stop token is propagated to the task for cooperative cancellation.
564  
    The handler `h1` is called with the result on success, and optionally
564  
    The handler `h1` is called with the result on success, and optionally
565  
    with exception_ptr if it accepts that type.
565  
    with exception_ptr if it accepts that type.
566  

566  

567  
    @param ex The executor to execute the task on.
567  
    @param ex The executor to execute the task on.
568  
    @param st The stop token for cooperative cancellation.
568  
    @param st The stop token for cooperative cancellation.
569  
    @param h1 The handler to invoke with the result (and optionally exception).
569  
    @param h1 The handler to invoke with the result (and optionally exception).
570  

570  

571  
    @return A wrapper that accepts a `task<T>` for immediate execution.
571  
    @return A wrapper that accepts a `task<T>` for immediate execution.
572  

572  

573  
    @see task
573  
    @see task
574  
    @see executor
574  
    @see executor
575  
*/
575  
*/
576  
template<Executor Ex, class H1>
576  
template<Executor Ex, class H1>
577  
[[nodiscard]] auto
577  
[[nodiscard]] auto
578  
run_async(Ex ex, std::stop_token st, H1 h1)
578  
run_async(Ex ex, std::stop_token st, H1 h1)
579  
{
579  
{
580  
    auto* mr = ex.context().get_frame_allocator();
580  
    auto* mr = ex.context().get_frame_allocator();
581  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
581  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
582  
        std::move(ex),
582  
        std::move(ex),
583  
        std::move(st),
583  
        std::move(st),
584  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
584  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
585  
        mr);
585  
        mr);
586  
}
586  
}
587  

587  

588  
/** Asynchronously launch a lazy task with stop token and separate handlers.
588  
/** Asynchronously launch a lazy task with stop token and separate handlers.
589  

589  

590  
    The stop token is propagated to the task for cooperative cancellation.
590  
    The stop token is propagated to the task for cooperative cancellation.
591  
    The handler `h1` is called on success, `h2` on failure.
591  
    The handler `h1` is called on success, `h2` on failure.
592  

592  

593  
    @param ex The executor to execute the task on.
593  
    @param ex The executor to execute the task on.
594  
    @param st The stop token for cooperative cancellation.
594  
    @param st The stop token for cooperative cancellation.
595  
    @param h1 The handler to invoke with the result on success.
595  
    @param h1 The handler to invoke with the result on success.
596  
    @param h2 The handler to invoke with the exception on failure.
596  
    @param h2 The handler to invoke with the exception on failure.
597  

597  

598  
    @return A wrapper that accepts a `task<T>` for immediate execution.
598  
    @return A wrapper that accepts a `task<T>` for immediate execution.
599  

599  

600  
    @see task
600  
    @see task
601  
    @see executor
601  
    @see executor
602  
*/
602  
*/
603  
template<Executor Ex, class H1, class H2>
603  
template<Executor Ex, class H1, class H2>
604  
[[nodiscard]] auto
604  
[[nodiscard]] auto
605  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
605  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
606  
{
606  
{
607  
    auto* mr = ex.context().get_frame_allocator();
607  
    auto* mr = ex.context().get_frame_allocator();
608  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
608  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
609  
        std::move(ex),
609  
        std::move(ex),
610  
        std::move(st),
610  
        std::move(st),
611  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
611  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
612  
        mr);
612  
        mr);
613  
}
613  
}
614  

614  

615  
// Ex + memory_resource*
615  
// Ex + memory_resource*
616  

616  

617  
/** Asynchronously launch a lazy task with custom memory resource.
617  
/** Asynchronously launch a lazy task with custom memory resource.
618  

618  

619  
    The memory resource is used for coroutine frame allocation. The caller
619  
    The memory resource is used for coroutine frame allocation. The caller
620  
    is responsible for ensuring the memory resource outlives all tasks.
620  
    is responsible for ensuring the memory resource outlives all tasks.
621  

621  

622  
    @param ex The executor to execute the task on.
622  
    @param ex The executor to execute the task on.
623  
    @param mr The memory resource for frame allocation.
623  
    @param mr The memory resource for frame allocation.
624  

624  

625  
    @return A wrapper that accepts a `task<T>` for immediate execution.
625  
    @return A wrapper that accepts a `task<T>` for immediate execution.
626  

626  

627  
    @see task
627  
    @see task
628  
    @see executor
628  
    @see executor
629  
*/
629  
*/
630  
template<Executor Ex>
630  
template<Executor Ex>
631  
[[nodiscard]] auto
631  
[[nodiscard]] auto
632  
run_async(Ex ex, std::pmr::memory_resource* mr)
632  
run_async(Ex ex, std::pmr::memory_resource* mr)
633  
{
633  
{
634  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
634  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
635  
        std::move(ex),
635  
        std::move(ex),
636  
        std::stop_token{},
636  
        std::stop_token{},
637  
        detail::default_handler{},
637  
        detail::default_handler{},
638  
        mr);
638  
        mr);
639  
}
639  
}
640  

640  

641  
/** Asynchronously launch a lazy task with memory resource and handler.
641  
/** Asynchronously launch a lazy task with memory resource and handler.
642  

642  

643  
    @param ex The executor to execute the task on.
643  
    @param ex The executor to execute the task on.
644  
    @param mr The memory resource for frame allocation.
644  
    @param mr The memory resource for frame allocation.
645  
    @param h1 The handler to invoke with the result (and optionally exception).
645  
    @param h1 The handler to invoke with the result (and optionally exception).
646  

646  

647  
    @return A wrapper that accepts a `task<T>` for immediate execution.
647  
    @return A wrapper that accepts a `task<T>` for immediate execution.
648  

648  

649  
    @see task
649  
    @see task
650  
    @see executor
650  
    @see executor
651  
*/
651  
*/
652  
template<Executor Ex, class H1>
652  
template<Executor Ex, class H1>
653  
[[nodiscard]] auto
653  
[[nodiscard]] auto
654  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
654  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
655  
{
655  
{
656  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
656  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
657  
        std::move(ex),
657  
        std::move(ex),
658  
        std::stop_token{},
658  
        std::stop_token{},
659  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
659  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
660  
        mr);
660  
        mr);
661  
}
661  
}
662  

662  

663  
/** Asynchronously launch a lazy task with memory resource and handlers.
663  
/** Asynchronously launch a lazy task with memory resource and handlers.
664  

664  

665  
    @param ex The executor to execute the task on.
665  
    @param ex The executor to execute the task on.
666  
    @param mr The memory resource for frame allocation.
666  
    @param mr The memory resource for frame allocation.
667  
    @param h1 The handler to invoke with the result on success.
667  
    @param h1 The handler to invoke with the result on success.
668  
    @param h2 The handler to invoke with the exception on failure.
668  
    @param h2 The handler to invoke with the exception on failure.
669  

669  

670  
    @return A wrapper that accepts a `task<T>` for immediate execution.
670  
    @return A wrapper that accepts a `task<T>` for immediate execution.
671  

671  

672  
    @see task
672  
    @see task
673  
    @see executor
673  
    @see executor
674  
*/
674  
*/
675  
template<Executor Ex, class H1, class H2>
675  
template<Executor Ex, class H1, class H2>
676  
[[nodiscard]] auto
676  
[[nodiscard]] auto
677  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
677  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
678  
{
678  
{
679  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
679  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
680  
        std::move(ex),
680  
        std::move(ex),
681  
        std::stop_token{},
681  
        std::stop_token{},
682  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
682  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
683  
        mr);
683  
        mr);
684  
}
684  
}
685  

685  

686  
// Ex + stop_token + memory_resource*
686  
// Ex + stop_token + memory_resource*
687  

687  

688  
/** Asynchronously launch a lazy task with stop token and memory resource.
688  
/** Asynchronously launch a lazy task with stop token and memory resource.
689  

689  

690  
    @param ex The executor to execute the task on.
690  
    @param ex The executor to execute the task on.
691  
    @param st The stop token for cooperative cancellation.
691  
    @param st The stop token for cooperative cancellation.
692  
    @param mr The memory resource for frame allocation.
692  
    @param mr The memory resource for frame allocation.
693  

693  

694  
    @return A wrapper that accepts a `task<T>` for immediate execution.
694  
    @return A wrapper that accepts a `task<T>` for immediate execution.
695  

695  

696  
    @see task
696  
    @see task
697  
    @see executor
697  
    @see executor
698  
*/
698  
*/
699  
template<Executor Ex>
699  
template<Executor Ex>
700  
[[nodiscard]] auto
700  
[[nodiscard]] auto
701  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
701  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
702  
{
702  
{
703  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
703  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
704  
        std::move(ex),
704  
        std::move(ex),
705  
        std::move(st),
705  
        std::move(st),
706  
        detail::default_handler{},
706  
        detail::default_handler{},
707  
        mr);
707  
        mr);
708  
}
708  
}
709  

709  

710  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
710  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
711  

711  

712  
    @param ex The executor to execute the task on.
712  
    @param ex The executor to execute the task on.
713  
    @param st The stop token for cooperative cancellation.
713  
    @param st The stop token for cooperative cancellation.
714  
    @param mr The memory resource for frame allocation.
714  
    @param mr The memory resource for frame allocation.
715  
    @param h1 The handler to invoke with the result (and optionally exception).
715  
    @param h1 The handler to invoke with the result (and optionally exception).
716  

716  

717  
    @return A wrapper that accepts a `task<T>` for immediate execution.
717  
    @return A wrapper that accepts a `task<T>` for immediate execution.
718  

718  

719  
    @see task
719  
    @see task
720  
    @see executor
720  
    @see executor
721  
*/
721  
*/
722  
template<Executor Ex, class H1>
722  
template<Executor Ex, class H1>
723  
[[nodiscard]] auto
723  
[[nodiscard]] auto
724  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
724  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
725  
{
725  
{
726  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
726  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
727  
        std::move(ex),
727  
        std::move(ex),
728  
        std::move(st),
728  
        std::move(st),
729  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
729  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
730  
        mr);
730  
        mr);
731  
}
731  
}
732  

732  

733  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
733  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
734  

734  

735  
    @param ex The executor to execute the task on.
735  
    @param ex The executor to execute the task on.
736  
    @param st The stop token for cooperative cancellation.
736  
    @param st The stop token for cooperative cancellation.
737  
    @param mr The memory resource for frame allocation.
737  
    @param mr The memory resource for frame allocation.
738  
    @param h1 The handler to invoke with the result on success.
738  
    @param h1 The handler to invoke with the result on success.
739  
    @param h2 The handler to invoke with the exception on failure.
739  
    @param h2 The handler to invoke with the exception on failure.
740  

740  

741  
    @return A wrapper that accepts a `task<T>` for immediate execution.
741  
    @return A wrapper that accepts a `task<T>` for immediate execution.
742  

742  

743  
    @see task
743  
    @see task
744  
    @see executor
744  
    @see executor
745  
*/
745  
*/
746  
template<Executor Ex, class H1, class H2>
746  
template<Executor Ex, class H1, class H2>
747  
[[nodiscard]] auto
747  
[[nodiscard]] auto
748  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
748  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
749  
{
749  
{
750  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
750  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
751  
        std::move(ex),
751  
        std::move(ex),
752  
        std::move(st),
752  
        std::move(st),
753  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
753  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
754  
        mr);
754  
        mr);
755  
}
755  
}
756  

756  

757  
// Ex + standard Allocator (value type)
757  
// Ex + standard Allocator (value type)
758  

758  

759  
/** Asynchronously launch a lazy task with custom allocator.
759  
/** Asynchronously launch a lazy task with custom allocator.
760  

760  

761  
    The allocator is wrapped in a frame_memory_resource and stored in the
761  
    The allocator is wrapped in a frame_memory_resource and stored in the
762  
    run_async_trampoline, ensuring it outlives all coroutine frames.
762  
    run_async_trampoline, ensuring it outlives all coroutine frames.
763  

763  

764  
    @param ex The executor to execute the task on.
764  
    @param ex The executor to execute the task on.
765  
    @param alloc The allocator for frame allocation (copied and stored).
765  
    @param alloc The allocator for frame allocation (copied and stored).
766  

766  

767  
    @return A wrapper that accepts a `task<T>` for immediate execution.
767  
    @return A wrapper that accepts a `task<T>` for immediate execution.
768  

768  

769  
    @see task
769  
    @see task
770  
    @see executor
770  
    @see executor
771  
*/
771  
*/
772  
template<Executor Ex, detail::Allocator Alloc>
772  
template<Executor Ex, detail::Allocator Alloc>
773  
[[nodiscard]] auto
773  
[[nodiscard]] auto
774  
run_async(Ex ex, Alloc alloc)
774  
run_async(Ex ex, Alloc alloc)
775  
{
775  
{
776  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
776  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
777  
        std::move(ex),
777  
        std::move(ex),
778  
        std::stop_token{},
778  
        std::stop_token{},
779  
        detail::default_handler{},
779  
        detail::default_handler{},
780  
        std::move(alloc));
780  
        std::move(alloc));
781  
}
781  
}
782  

782  

783  
/** Asynchronously launch a lazy task with allocator and handler.
783  
/** Asynchronously launch a lazy task with allocator and handler.
784  

784  

785  
    @param ex The executor to execute the task on.
785  
    @param ex The executor to execute the task on.
786  
    @param alloc The allocator for frame allocation (copied and stored).
786  
    @param alloc The allocator for frame allocation (copied and stored).
787  
    @param h1 The handler to invoke with the result (and optionally exception).
787  
    @param h1 The handler to invoke with the result (and optionally exception).
788  

788  

789  
    @return A wrapper that accepts a `task<T>` for immediate execution.
789  
    @return A wrapper that accepts a `task<T>` for immediate execution.
790  

790  

791  
    @see task
791  
    @see task
792  
    @see executor
792  
    @see executor
793  
*/
793  
*/
794  
template<Executor Ex, detail::Allocator Alloc, class H1>
794  
template<Executor Ex, detail::Allocator Alloc, class H1>
795  
[[nodiscard]] auto
795  
[[nodiscard]] auto
796  
run_async(Ex ex, Alloc alloc, H1 h1)
796  
run_async(Ex ex, Alloc alloc, H1 h1)
797  
{
797  
{
798  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
798  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
799  
        std::move(ex),
799  
        std::move(ex),
800  
        std::stop_token{},
800  
        std::stop_token{},
801  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
801  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
802  
        std::move(alloc));
802  
        std::move(alloc));
803  
}
803  
}
804  

804  

805  
/** Asynchronously launch a lazy task with allocator and handlers.
805  
/** Asynchronously launch a lazy task with allocator and handlers.
806  

806  

807  
    @param ex The executor to execute the task on.
807  
    @param ex The executor to execute the task on.
808  
    @param alloc The allocator for frame allocation (copied and stored).
808  
    @param alloc The allocator for frame allocation (copied and stored).
809  
    @param h1 The handler to invoke with the result on success.
809  
    @param h1 The handler to invoke with the result on success.
810  
    @param h2 The handler to invoke with the exception on failure.
810  
    @param h2 The handler to invoke with the exception on failure.
811  

811  

812  
    @return A wrapper that accepts a `task<T>` for immediate execution.
812  
    @return A wrapper that accepts a `task<T>` for immediate execution.
813  

813  

814  
    @see task
814  
    @see task
815  
    @see executor
815  
    @see executor
816  
*/
816  
*/
817  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
817  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
818  
[[nodiscard]] auto
818  
[[nodiscard]] auto
819  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
819  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
820  
{
820  
{
821  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
821  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
822  
        std::move(ex),
822  
        std::move(ex),
823  
        std::stop_token{},
823  
        std::stop_token{},
824  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
824  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
825  
        std::move(alloc));
825  
        std::move(alloc));
826  
}
826  
}
827  

827  

828  
// Ex + stop_token + standard Allocator
828  
// Ex + stop_token + standard Allocator
829  

829  

830  
/** Asynchronously launch a lazy task with stop token and allocator.
830  
/** Asynchronously launch a lazy task with stop token and allocator.
831  

831  

832  
    @param ex The executor to execute the task on.
832  
    @param ex The executor to execute the task on.
833  
    @param st The stop token for cooperative cancellation.
833  
    @param st The stop token for cooperative cancellation.
834  
    @param alloc The allocator for frame allocation (copied and stored).
834  
    @param alloc The allocator for frame allocation (copied and stored).
835  

835  

836  
    @return A wrapper that accepts a `task<T>` for immediate execution.
836  
    @return A wrapper that accepts a `task<T>` for immediate execution.
837  

837  

838  
    @see task
838  
    @see task
839  
    @see executor
839  
    @see executor
840  
*/
840  
*/
841  
template<Executor Ex, detail::Allocator Alloc>
841  
template<Executor Ex, detail::Allocator Alloc>
842  
[[nodiscard]] auto
842  
[[nodiscard]] auto
843  
run_async(Ex ex, std::stop_token st, Alloc alloc)
843  
run_async(Ex ex, std::stop_token st, Alloc alloc)
844  
{
844  
{
845  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
845  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
846  
        std::move(ex),
846  
        std::move(ex),
847  
        std::move(st),
847  
        std::move(st),
848  
        detail::default_handler{},
848  
        detail::default_handler{},
849  
        std::move(alloc));
849  
        std::move(alloc));
850  
}
850  
}
851  

851  

852  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
852  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
853  

853  

854  
    @param ex The executor to execute the task on.
854  
    @param ex The executor to execute the task on.
855  
    @param st The stop token for cooperative cancellation.
855  
    @param st The stop token for cooperative cancellation.
856  
    @param alloc The allocator for frame allocation (copied and stored).
856  
    @param alloc The allocator for frame allocation (copied and stored).
857  
    @param h1 The handler to invoke with the result (and optionally exception).
857  
    @param h1 The handler to invoke with the result (and optionally exception).
858  

858  

859  
    @return A wrapper that accepts a `task<T>` for immediate execution.
859  
    @return A wrapper that accepts a `task<T>` for immediate execution.
860  

860  

861  
    @see task
861  
    @see task
862  
    @see executor
862  
    @see executor
863  
*/
863  
*/
864  
template<Executor Ex, detail::Allocator Alloc, class H1>
864  
template<Executor Ex, detail::Allocator Alloc, class H1>
865  
[[nodiscard]] auto
865  
[[nodiscard]] auto
866  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
866  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
867  
{
867  
{
868  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
868  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
869  
        std::move(ex),
869  
        std::move(ex),
870  
        std::move(st),
870  
        std::move(st),
871  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
871  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
872  
        std::move(alloc));
872  
        std::move(alloc));
873  
}
873  
}
874  

874  

875  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
875  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
876  

876  

877  
    @param ex The executor to execute the task on.
877  
    @param ex The executor to execute the task on.
878  
    @param st The stop token for cooperative cancellation.
878  
    @param st The stop token for cooperative cancellation.
879  
    @param alloc The allocator for frame allocation (copied and stored).
879  
    @param alloc The allocator for frame allocation (copied and stored).
880  
    @param h1 The handler to invoke with the result on success.
880  
    @param h1 The handler to invoke with the result on success.
881  
    @param h2 The handler to invoke with the exception on failure.
881  
    @param h2 The handler to invoke with the exception on failure.
882  

882  

883  
    @return A wrapper that accepts a `task<T>` for immediate execution.
883  
    @return A wrapper that accepts a `task<T>` for immediate execution.
884  

884  

885  
    @see task
885  
    @see task
886  
    @see executor
886  
    @see executor
887  
*/
887  
*/
888  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
888  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
889  
[[nodiscard]] auto
889  
[[nodiscard]] auto
890  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
890  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
891  
{
891  
{
892  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
892  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
893  
        std::move(ex),
893  
        std::move(ex),
894  
        std::move(st),
894  
        std::move(st),
895  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
895  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
896  
        std::move(alloc));
896  
        std::move(alloc));
897  
}
897  
}
898  

898  

899  
} // namespace capy
899  
} // namespace capy
900  
} // namespace boost
900  
} // namespace boost
901  

901  

902  
#endif
902  
#endif