libs/capy/include/boost/capy/ex/run.hpp

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