TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
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)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_RUN_HPP
11 : #define BOOST_CAPY_RUN_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/detail/await_suspend_helper.hpp>
15 : #include <boost/capy/detail/run.hpp>
16 : #include <boost/capy/concept/executor.hpp>
17 : #include <boost/capy/concept/io_runnable.hpp>
18 : #include <boost/capy/ex/executor_ref.hpp>
19 : #include <coroutine>
20 : #include <boost/capy/ex/frame_allocator.hpp>
21 : #include <boost/capy/ex/io_env.hpp>
22 :
23 : #include <memory_resource>
24 : #include <stop_token>
25 : #include <type_traits>
26 : #include <utility>
27 : #include <variant>
28 :
29 : /*
30 : Allocator Lifetime Strategy
31 : ===========================
32 :
33 : When using run() with a custom allocator:
34 :
35 : co_await run(ex, alloc)(my_task());
36 :
37 : The evaluation order is:
38 : 1. run(ex, alloc) creates a temporary wrapper
39 : 2. my_task() allocates its coroutine frame using TLS
40 : 3. operator() returns an awaitable
41 : 4. Wrapper temporary is DESTROYED
42 : 5. co_await suspends caller, resumes task
43 : 6. Task body executes (wrapper is already dead!)
44 :
45 : Problem: The wrapper's frame_memory_resource dies before the task
46 : body runs. When initial_suspend::await_resume() restores TLS from
47 : the saved pointer, it would point to dead memory.
48 :
49 : Solution: Store a COPY of the allocator in the awaitable (not just
50 : the wrapper). The co_await mechanism extends the awaitable's lifetime
51 : until the await completes. In await_suspend, we overwrite the promise's
52 : saved frame_allocator pointer to point to the awaitable's resource.
53 :
54 : This works because standard allocator copies are equivalent - memory
55 : allocated with one copy can be deallocated with another copy. The
56 : task's own frame uses the footer-stored pointer (safe), while nested
57 : task creation uses TLS pointing to the awaitable's resource (also safe).
58 : */
59 :
60 : namespace boost::capy::detail {
61 :
62 : /** Minimal coroutine that dispatches through the caller's executor.
63 :
64 : Sits between the inner task and the parent when executors
65 : diverge. The inner task's `final_suspend` resumes this
66 : trampoline via symmetric transfer. The trampoline's own
67 : `final_suspend` dispatches the parent through the caller's
68 : executor to restore the correct execution context.
69 :
70 : The trampoline never touches the task's result.
71 : */
72 : struct dispatch_trampoline
73 : {
74 : struct promise_type
75 : {
76 : executor_ref caller_ex_;
77 : std::coroutine_handle<> parent_;
78 :
79 HIT 12 : dispatch_trampoline get_return_object() noexcept
80 : {
81 : return dispatch_trampoline{
82 12 : std::coroutine_handle<promise_type>::from_promise(*this)};
83 : }
84 :
85 12 : std::suspend_always initial_suspend() noexcept { return {}; }
86 :
87 12 : auto final_suspend() noexcept
88 : {
89 : struct awaiter
90 : {
91 : promise_type* p_;
92 12 : bool await_ready() const noexcept { return false; }
93 :
94 12 : auto await_suspend(
95 : std::coroutine_handle<>) noexcept
96 : {
97 12 : return detail::symmetric_transfer(
98 24 : p_->caller_ex_.dispatch(p_->parent_));
99 : }
100 :
101 MIS 0 : void await_resume() const noexcept {}
102 : };
103 HIT 12 : return awaiter{this};
104 : }
105 :
106 12 : void return_void() noexcept {}
107 : void unhandled_exception() noexcept {}
108 : };
109 :
110 : std::coroutine_handle<promise_type> h_{nullptr};
111 :
112 12 : dispatch_trampoline() noexcept = default;
113 :
114 36 : ~dispatch_trampoline()
115 : {
116 36 : if(h_) h_.destroy();
117 36 : }
118 :
119 : dispatch_trampoline(dispatch_trampoline const&) = delete;
120 : dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
121 :
122 12 : dispatch_trampoline(dispatch_trampoline&& o) noexcept
123 12 : : h_(std::exchange(o.h_, nullptr)) {}
124 :
125 12 : dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
126 : {
127 12 : if(this != &o)
128 : {
129 12 : if(h_) h_.destroy();
130 12 : h_ = std::exchange(o.h_, nullptr);
131 : }
132 12 : return *this;
133 : }
134 :
135 : private:
136 12 : explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
137 12 : : h_(h) {}
138 : };
139 :
140 12 : inline dispatch_trampoline make_dispatch_trampoline()
141 : {
142 : co_return;
143 24 : }
144 :
145 : /** Awaitable that binds an IoRunnable to a specific executor.
146 :
147 : Stores the executor and inner task by value. When co_awaited, the
148 : co_await expression's lifetime extension keeps both alive for the
149 : duration of the operation.
150 :
151 : A dispatch trampoline handles the executor switch on completion:
152 : the inner task's `final_suspend` resumes the trampoline, which
153 : dispatches back through the caller's executor.
154 :
155 : The `io_env` is owned by this awaitable and is guaranteed to
156 : outlive the inner task and all awaitables in its chain. Awaitables
157 : may store `io_env const*` without concern for dangling references.
158 :
159 : @tparam Task The IoRunnable type
160 : @tparam Ex The executor type
161 : @tparam InheritStopToken If true, inherit caller's stop token
162 : @tparam Alloc The allocator type (void for no allocator)
163 : */
164 : template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
165 : struct [[nodiscard]] run_awaitable_ex
166 : {
167 : Ex ex_;
168 : frame_memory_resource<Alloc> resource_;
169 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
170 : io_env env_;
171 : dispatch_trampoline tr_;
172 : Task inner_; // Last: destroyed first, while env_ is still valid
173 :
174 : // void allocator, inherit stop token
175 4 : run_awaitable_ex(Ex ex, Task inner)
176 : requires (InheritStopToken && std::is_void_v<Alloc>)
177 4 : : ex_(std::move(ex))
178 4 : , inner_(std::move(inner))
179 : {
180 4 : }
181 :
182 : // void allocator, explicit stop token
183 4 : run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
184 : requires (!InheritStopToken && std::is_void_v<Alloc>)
185 4 : : ex_(std::move(ex))
186 4 : , st_(std::move(st))
187 4 : , inner_(std::move(inner))
188 : {
189 4 : }
190 :
191 : // with allocator, inherit stop token (use template to avoid void parameter)
192 : template<class A>
193 : requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
194 2 : run_awaitable_ex(Ex ex, A alloc, Task inner)
195 2 : : ex_(std::move(ex))
196 2 : , resource_(std::move(alloc))
197 2 : , inner_(std::move(inner))
198 : {
199 2 : }
200 :
201 : // with allocator, explicit stop token (use template to avoid void parameter)
202 : template<class A>
203 : requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
204 2 : run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
205 2 : : ex_(std::move(ex))
206 2 : , resource_(std::move(alloc))
207 2 : , st_(std::move(st))
208 2 : , inner_(std::move(inner))
209 : {
210 2 : }
211 :
212 12 : bool await_ready() const noexcept
213 : {
214 12 : return inner_.await_ready();
215 : }
216 :
217 12 : decltype(auto) await_resume()
218 : {
219 12 : return inner_.await_resume();
220 : }
221 :
222 12 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
223 : {
224 12 : tr_ = make_dispatch_trampoline();
225 12 : tr_.h_.promise().caller_ex_ = caller_env->executor;
226 12 : tr_.h_.promise().parent_ = cont;
227 :
228 12 : auto h = inner_.handle();
229 12 : auto& p = h.promise();
230 12 : p.set_continuation(tr_.h_);
231 :
232 12 : env_.executor = ex_;
233 : if constexpr (InheritStopToken)
234 6 : env_.stop_token = caller_env->stop_token;
235 : else
236 6 : env_.stop_token = st_;
237 :
238 : if constexpr (!std::is_void_v<Alloc>)
239 4 : env_.frame_allocator = resource_.get();
240 : else
241 8 : env_.frame_allocator = caller_env->frame_allocator;
242 :
243 12 : p.set_environment(&env_);
244 24 : return h;
245 : }
246 :
247 : // Non-copyable
248 : run_awaitable_ex(run_awaitable_ex const&) = delete;
249 : run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
250 :
251 : // Movable (no noexcept - Task may throw)
252 12 : run_awaitable_ex(run_awaitable_ex&&) = default;
253 : run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
254 : };
255 :
256 : /** Awaitable that runs a task with optional stop_token override.
257 :
258 : Does NOT store an executor - the task inherits the caller's executor
259 : directly. Executors always match, so no dispatch trampoline is needed.
260 : The inner task's `final_suspend` resumes the parent directly via
261 : unconditional symmetric transfer.
262 :
263 : @tparam Task The IoRunnable type
264 : @tparam InheritStopToken If true, inherit caller's stop token
265 : @tparam Alloc The allocator type (void for no allocator)
266 : */
267 : template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
268 : struct [[nodiscard]] run_awaitable
269 : {
270 : frame_memory_resource<Alloc> resource_;
271 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
272 : io_env env_;
273 : Task inner_; // Last: destroyed first, while env_ is still valid
274 :
275 : // void allocator, inherit stop token
276 : explicit run_awaitable(Task inner)
277 : requires (InheritStopToken && std::is_void_v<Alloc>)
278 : : inner_(std::move(inner))
279 : {
280 : }
281 :
282 : // void allocator, explicit stop token
283 1 : run_awaitable(Task inner, std::stop_token st)
284 : requires (!InheritStopToken && std::is_void_v<Alloc>)
285 1 : : st_(std::move(st))
286 1 : , inner_(std::move(inner))
287 : {
288 1 : }
289 :
290 : // with allocator, inherit stop token (use template to avoid void parameter)
291 : template<class A>
292 : requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
293 3 : run_awaitable(A alloc, Task inner)
294 3 : : resource_(std::move(alloc))
295 3 : , inner_(std::move(inner))
296 : {
297 3 : }
298 :
299 : // with allocator, explicit stop token (use template to avoid void parameter)
300 : template<class A>
301 : requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
302 2 : run_awaitable(A alloc, Task inner, std::stop_token st)
303 2 : : resource_(std::move(alloc))
304 2 : , st_(std::move(st))
305 2 : , inner_(std::move(inner))
306 : {
307 2 : }
308 :
309 6 : bool await_ready() const noexcept
310 : {
311 6 : return inner_.await_ready();
312 : }
313 :
314 6 : decltype(auto) await_resume()
315 : {
316 6 : return inner_.await_resume();
317 : }
318 :
319 6 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
320 : {
321 6 : auto h = inner_.handle();
322 6 : auto& p = h.promise();
323 6 : p.set_continuation(cont);
324 :
325 6 : env_.executor = caller_env->executor;
326 : if constexpr (InheritStopToken)
327 3 : env_.stop_token = caller_env->stop_token;
328 : else
329 3 : env_.stop_token = st_;
330 :
331 : if constexpr (!std::is_void_v<Alloc>)
332 5 : env_.frame_allocator = resource_.get();
333 : else
334 1 : env_.frame_allocator = caller_env->frame_allocator;
335 :
336 6 : p.set_environment(&env_);
337 6 : return h;
338 : }
339 :
340 : // Non-copyable
341 : run_awaitable(run_awaitable const&) = delete;
342 : run_awaitable& operator=(run_awaitable const&) = delete;
343 :
344 : // Movable (no noexcept - Task may throw)
345 6 : run_awaitable(run_awaitable&&) = default;
346 : run_awaitable& operator=(run_awaitable&&) = default;
347 : };
348 :
349 : /** Wrapper returned by run(ex, ...) that accepts a task for execution.
350 :
351 : @tparam Ex The executor type.
352 : @tparam InheritStopToken If true, inherit caller's stop token.
353 : @tparam Alloc The allocator type (void for no allocator).
354 : */
355 : template<Executor Ex, bool InheritStopToken, class Alloc>
356 : class [[nodiscard]] run_wrapper_ex
357 : {
358 : Ex ex_;
359 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
360 : frame_memory_resource<Alloc> resource_;
361 : Alloc alloc_; // Copy to pass to awaitable
362 :
363 : public:
364 1 : run_wrapper_ex(Ex ex, Alloc alloc)
365 : requires InheritStopToken
366 1 : : ex_(std::move(ex))
367 1 : , resource_(alloc)
368 1 : , alloc_(std::move(alloc))
369 : {
370 1 : set_current_frame_allocator(&resource_);
371 1 : }
372 :
373 1 : run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
374 : requires (!InheritStopToken)
375 1 : : ex_(std::move(ex))
376 1 : , st_(std::move(st))
377 1 : , resource_(alloc)
378 1 : , alloc_(std::move(alloc))
379 : {
380 1 : set_current_frame_allocator(&resource_);
381 1 : }
382 :
383 : // Non-copyable, non-movable (must be used immediately)
384 : run_wrapper_ex(run_wrapper_ex const&) = delete;
385 : run_wrapper_ex(run_wrapper_ex&&) = delete;
386 : run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
387 : run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
388 :
389 : template<IoRunnable Task>
390 2 : [[nodiscard]] auto operator()(Task t) &&
391 : {
392 : if constexpr (InheritStopToken)
393 : return run_awaitable_ex<Task, Ex, true, Alloc>{
394 1 : std::move(ex_), std::move(alloc_), std::move(t)};
395 : else
396 : return run_awaitable_ex<Task, Ex, false, Alloc>{
397 1 : std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
398 : }
399 : };
400 :
401 : /// Specialization for memory_resource* - stores pointer directly.
402 : template<Executor Ex, bool InheritStopToken>
403 : class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
404 : {
405 : Ex ex_;
406 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
407 : std::pmr::memory_resource* mr_;
408 :
409 : public:
410 1 : run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
411 : requires InheritStopToken
412 1 : : ex_(std::move(ex))
413 1 : , mr_(mr)
414 : {
415 1 : set_current_frame_allocator(mr_);
416 1 : }
417 :
418 1 : run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
419 : requires (!InheritStopToken)
420 1 : : ex_(std::move(ex))
421 1 : , st_(std::move(st))
422 1 : , mr_(mr)
423 : {
424 1 : set_current_frame_allocator(mr_);
425 1 : }
426 :
427 : // Non-copyable, non-movable (must be used immediately)
428 : run_wrapper_ex(run_wrapper_ex const&) = delete;
429 : run_wrapper_ex(run_wrapper_ex&&) = delete;
430 : run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
431 : run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
432 :
433 : template<IoRunnable Task>
434 2 : [[nodiscard]] auto operator()(Task t) &&
435 : {
436 : if constexpr (InheritStopToken)
437 : return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
438 1 : std::move(ex_), mr_, std::move(t)};
439 : else
440 : return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
441 1 : std::move(ex_), mr_, std::move(t), std::move(st_)};
442 : }
443 : };
444 :
445 : /// Specialization for no allocator (void).
446 : template<Executor Ex, bool InheritStopToken>
447 : class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
448 : {
449 : Ex ex_;
450 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
451 :
452 : public:
453 4 : explicit run_wrapper_ex(Ex ex)
454 : requires InheritStopToken
455 4 : : ex_(std::move(ex))
456 : {
457 4 : }
458 :
459 4 : run_wrapper_ex(Ex ex, std::stop_token st)
460 : requires (!InheritStopToken)
461 4 : : ex_(std::move(ex))
462 4 : , st_(std::move(st))
463 : {
464 4 : }
465 :
466 : // Non-copyable, non-movable (must be used immediately)
467 : run_wrapper_ex(run_wrapper_ex const&) = delete;
468 : run_wrapper_ex(run_wrapper_ex&&) = delete;
469 : run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
470 : run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
471 :
472 : template<IoRunnable Task>
473 8 : [[nodiscard]] auto operator()(Task t) &&
474 : {
475 : if constexpr (InheritStopToken)
476 : return run_awaitable_ex<Task, Ex, true>{
477 4 : std::move(ex_), std::move(t)};
478 : else
479 : return run_awaitable_ex<Task, Ex, false>{
480 4 : std::move(ex_), std::move(t), std::move(st_)};
481 : }
482 : };
483 :
484 : /** Wrapper returned by run(st) or run(alloc) that accepts a task.
485 :
486 : @tparam InheritStopToken If true, inherit caller's stop token.
487 : @tparam Alloc The allocator type (void for no allocator).
488 : */
489 : template<bool InheritStopToken, class Alloc>
490 : class [[nodiscard]] run_wrapper
491 : {
492 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
493 : frame_memory_resource<Alloc> resource_;
494 : Alloc alloc_; // Copy to pass to awaitable
495 :
496 : public:
497 1 : explicit run_wrapper(Alloc alloc)
498 : requires InheritStopToken
499 1 : : resource_(alloc)
500 1 : , alloc_(std::move(alloc))
501 : {
502 1 : set_current_frame_allocator(&resource_);
503 1 : }
504 :
505 1 : run_wrapper(std::stop_token st, Alloc alloc)
506 : requires (!InheritStopToken)
507 1 : : st_(std::move(st))
508 1 : , resource_(alloc)
509 1 : , alloc_(std::move(alloc))
510 : {
511 1 : set_current_frame_allocator(&resource_);
512 1 : }
513 :
514 : // Non-copyable, non-movable (must be used immediately)
515 : run_wrapper(run_wrapper const&) = delete;
516 : run_wrapper(run_wrapper&&) = delete;
517 : run_wrapper& operator=(run_wrapper const&) = delete;
518 : run_wrapper& operator=(run_wrapper&&) = delete;
519 :
520 : template<IoRunnable Task>
521 2 : [[nodiscard]] auto operator()(Task t) &&
522 : {
523 : if constexpr (InheritStopToken)
524 : return run_awaitable<Task, true, Alloc>{
525 1 : std::move(alloc_), std::move(t)};
526 : else
527 : return run_awaitable<Task, false, Alloc>{
528 1 : std::move(alloc_), std::move(t), std::move(st_)};
529 : }
530 : };
531 :
532 : /// Specialization for memory_resource* - stores pointer directly.
533 : template<bool InheritStopToken>
534 : class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
535 : {
536 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
537 : std::pmr::memory_resource* mr_;
538 :
539 : public:
540 2 : explicit run_wrapper(std::pmr::memory_resource* mr)
541 : requires InheritStopToken
542 2 : : mr_(mr)
543 : {
544 2 : set_current_frame_allocator(mr_);
545 2 : }
546 :
547 1 : run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
548 : requires (!InheritStopToken)
549 1 : : st_(std::move(st))
550 1 : , mr_(mr)
551 : {
552 1 : set_current_frame_allocator(mr_);
553 1 : }
554 :
555 : // Non-copyable, non-movable (must be used immediately)
556 : run_wrapper(run_wrapper const&) = delete;
557 : run_wrapper(run_wrapper&&) = delete;
558 : run_wrapper& operator=(run_wrapper const&) = delete;
559 : run_wrapper& operator=(run_wrapper&&) = delete;
560 :
561 : template<IoRunnable Task>
562 3 : [[nodiscard]] auto operator()(Task t) &&
563 : {
564 : if constexpr (InheritStopToken)
565 : return run_awaitable<Task, true, std::pmr::memory_resource*>{
566 2 : mr_, std::move(t)};
567 : else
568 : return run_awaitable<Task, false, std::pmr::memory_resource*>{
569 1 : mr_, std::move(t), std::move(st_)};
570 : }
571 : };
572 :
573 : /// Specialization for stop_token only (no allocator).
574 : template<>
575 : class [[nodiscard]] run_wrapper<false, void>
576 : {
577 : std::stop_token st_;
578 :
579 : public:
580 1 : explicit run_wrapper(std::stop_token st)
581 1 : : st_(std::move(st))
582 : {
583 1 : }
584 :
585 : // Non-copyable, non-movable (must be used immediately)
586 : run_wrapper(run_wrapper const&) = delete;
587 : run_wrapper(run_wrapper&&) = delete;
588 : run_wrapper& operator=(run_wrapper const&) = delete;
589 : run_wrapper& operator=(run_wrapper&&) = delete;
590 :
591 : template<IoRunnable Task>
592 1 : [[nodiscard]] auto operator()(Task t) &&
593 : {
594 1 : return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
595 : }
596 : };
597 :
598 : } // namespace boost::capy::detail
599 :
600 : namespace boost::capy {
601 :
602 : /** Bind a task to execute on a specific executor.
603 :
604 : Returns a wrapper that accepts a task and produces an awaitable.
605 : When co_awaited, the task runs on the specified executor.
606 :
607 : @par Example
608 : @code
609 : co_await run(other_executor)(my_task());
610 : @endcode
611 :
612 : @param ex The executor on which the task should run.
613 :
614 : @return A wrapper that accepts a task for execution.
615 :
616 : @see task
617 : @see executor
618 : */
619 : template<Executor Ex>
620 : [[nodiscard]] auto
621 4 : run(Ex ex)
622 : {
623 4 : return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
624 : }
625 :
626 : /** Bind a task to an executor with a stop token.
627 :
628 : @param ex The executor on which the task should run.
629 : @param st The stop token for cooperative cancellation.
630 :
631 : @return A wrapper that accepts a task for execution.
632 : */
633 : template<Executor Ex>
634 : [[nodiscard]] auto
635 4 : run(Ex ex, std::stop_token st)
636 : {
637 : return detail::run_wrapper_ex<Ex, false, void>{
638 4 : std::move(ex), std::move(st)};
639 : }
640 :
641 : /** Bind a task to an executor with a memory resource.
642 :
643 : @param ex The executor on which the task should run.
644 : @param mr The memory resource for frame allocation.
645 :
646 : @return A wrapper that accepts a task for execution.
647 : */
648 : template<Executor Ex>
649 : [[nodiscard]] auto
650 1 : run(Ex ex, std::pmr::memory_resource* mr)
651 : {
652 : return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
653 1 : std::move(ex), mr};
654 : }
655 :
656 : /** Bind a task to an executor with a standard allocator.
657 :
658 : @param ex The executor on which the task should run.
659 : @param alloc The allocator for frame allocation.
660 :
661 : @return A wrapper that accepts a task for execution.
662 : */
663 : template<Executor Ex, detail::Allocator Alloc>
664 : [[nodiscard]] auto
665 1 : run(Ex ex, Alloc alloc)
666 : {
667 : return detail::run_wrapper_ex<Ex, true, Alloc>{
668 1 : std::move(ex), std::move(alloc)};
669 : }
670 :
671 : /** Bind a task to an executor with stop token and memory resource.
672 :
673 : @param ex The executor on which the task should run.
674 : @param st The stop token for cooperative cancellation.
675 : @param mr The memory resource for frame allocation.
676 :
677 : @return A wrapper that accepts a task for execution.
678 : */
679 : template<Executor Ex>
680 : [[nodiscard]] auto
681 1 : run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
682 : {
683 : return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
684 1 : std::move(ex), std::move(st), mr};
685 : }
686 :
687 : /** Bind a task to an executor with stop token and standard allocator.
688 :
689 : @param ex The executor on which the task should run.
690 : @param st The stop token for cooperative cancellation.
691 : @param alloc The allocator for frame allocation.
692 :
693 : @return A wrapper that accepts a task for execution.
694 : */
695 : template<Executor Ex, detail::Allocator Alloc>
696 : [[nodiscard]] auto
697 1 : run(Ex ex, std::stop_token st, Alloc alloc)
698 : {
699 : return detail::run_wrapper_ex<Ex, false, Alloc>{
700 1 : std::move(ex), std::move(st), std::move(alloc)};
701 : }
702 :
703 : /** Run a task with a custom stop token.
704 :
705 : The task inherits the caller's executor. Only the stop token
706 : is overridden.
707 :
708 : @par Example
709 : @code
710 : std::stop_source source;
711 : co_await run(source.get_token())(cancellable_task());
712 : @endcode
713 :
714 : @param st The stop token for cooperative cancellation.
715 :
716 : @return A wrapper that accepts a task for execution.
717 : */
718 : [[nodiscard]] inline auto
719 1 : run(std::stop_token st)
720 : {
721 1 : return detail::run_wrapper<false, void>{std::move(st)};
722 : }
723 :
724 : /** Run a task with a custom memory resource.
725 :
726 : The task inherits the caller's executor. The memory resource
727 : is used for nested frame allocations.
728 :
729 : @param mr The memory resource for frame allocation.
730 :
731 : @return A wrapper that accepts a task for execution.
732 : */
733 : [[nodiscard]] inline auto
734 2 : run(std::pmr::memory_resource* mr)
735 : {
736 2 : return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
737 : }
738 :
739 : /** Run a task with a custom standard allocator.
740 :
741 : The task inherits the caller's executor. The allocator is used
742 : for nested frame allocations.
743 :
744 : @param alloc The allocator for frame allocation.
745 :
746 : @return A wrapper that accepts a task for execution.
747 : */
748 : template<detail::Allocator Alloc>
749 : [[nodiscard]] auto
750 1 : run(Alloc alloc)
751 : {
752 1 : return detail::run_wrapper<true, Alloc>{std::move(alloc)};
753 : }
754 :
755 : /** Run a task with stop token and memory resource.
756 :
757 : The task inherits the caller's executor.
758 :
759 : @param st The stop token for cooperative cancellation.
760 : @param mr The memory resource for frame allocation.
761 :
762 : @return A wrapper that accepts a task for execution.
763 : */
764 : [[nodiscard]] inline auto
765 1 : run(std::stop_token st, std::pmr::memory_resource* mr)
766 : {
767 : return detail::run_wrapper<false, std::pmr::memory_resource*>{
768 1 : std::move(st), mr};
769 : }
770 :
771 : /** Run a task with stop token and standard allocator.
772 :
773 : The task inherits the caller's executor.
774 :
775 : @param st The stop token for cooperative cancellation.
776 : @param alloc The allocator for frame allocation.
777 :
778 : @return A wrapper that accepts a task for execution.
779 : */
780 : template<detail::Allocator Alloc>
781 : [[nodiscard]] auto
782 1 : run(std::stop_token st, Alloc alloc)
783 : {
784 : return detail::run_wrapper<false, Alloc>{
785 1 : std::move(st), std::move(alloc)};
786 : }
787 :
788 : } // namespace boost::capy
789 :
790 : #endif
|