ex/run.hpp

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