tmc::fork_group()#
fork_group() provides an imperative interface for forking multiple awaitables. Unlike spawn_many() or spawn_tuple(), which require all awaitables to be known upfront, fork_group() allows you to incrementally fork awaitables and join them all at a later time.
Each awaitable is initiated immediately (concurrently) when fork() is called. The fork_group must be awaited to join all forked tasks before it goes out of scope.
Template Parameters#
fork_group() has two template parameters that determine its storage strategy:
MaxCount(default: 0): The maximum number of awaitables that will be forked.Result(default: void): The result type of the awaitables that will be forked.
Storage Strategies:
If
Resultisvoid, thenMaxCountmust be 0. No storage is needed, so an unlimited number of void-returning awaitables can be forked with no space overhead.If
Resultis non-void andMaxCountis non-zero, a fixed-sizestd::array<Result, MaxCount>is used. If fewer thanMaxCountawaitables are forked, the remaining results are default-initialized.If
Resultis non-void andMaxCountis 0, you must pass aRuntimeMaxCountparameter at construction. Astd::vector<Result>of this size is pre-allocated.
If Result is not default-constructible, each value will instead be wrapped into a std::optional<Result>.
Imperative Fork Interface#
.fork()#
Initiates an awaitable immediately on the specified executor and priority. The awaitable can be any awaitable type as long as its result type matches the fork_group’s Result type.
auto fg = tmc::fork_group();
fg.fork(task_void());
fg.fork(some_awaitable());
co_await std::move(fg);
.fork_clang()#
Similar to fork() but allows the forked task’s allocation to be elided via HALO (Heap Allocation eLision Optimization) when compiled with Clang 20+. See HALO for details.
IMPORTANT: You must co_await the result of fork_clang() immediately for HALO to be possible.
auto fg = tmc::fork_group();
co_await fg.fork_clang(task_void());
co_await fg.fork_clang(another_task());
co_await std::move(fg);
WARNING: Do not use fork_clang() in a loop, as Clang will try to reuse the same allocation for multiple active coroutines, causing crashes.
// WRONG - Will crash!
auto fg = tmc::fork_group();
for (int i = 0; i < 2; i++) {
co_await fg.fork_clang(task(i));
}
co_await std::move(fg);
// The equivalent without the loop works fine.
auto fg = tmc::fork_group();
co_await fg.fork_clang(task(0));
co_await fg.fork_clang(task(1));
co_await std::move(fg);
// Or you can use regular fork() in a loop
// (but HALO will not be performed)
auto fg = tmc::fork_group();
for (int i = 0; i < 2; i++) {
fg.fork(task(i));
}
co_await std::move(fg);
Awaitable Customizations#
The fork_group awaitable supports this Awaitable Customization:
resume_on()
Note that fork_group does not support run_on() or with_priority() on the group itself. Instead, you specify the executor and priority for each individual forked task via the fork() method parameters.
API Reference#
-
template<size_t MaxCount = 0, typename Result = void>
aw_fork_group<MaxCount, Result> tmc::fork_group()# Constructs an empty fork group with default template parameters.
MaxCountis the maximum number of awaitables that will be forked.If
Resultis void (default), thenMaxCountmust be 0. This allows you to fork an unlimited number of void-returning awaitables.If
Resultis non-void andMaxCountis non-zero, a fixed-size result array is used. If less thanMaxCountawaitables are forked, the remaining results will be default-initialized.If
Resultis non-void andMaxCountis 0, you must use thefork_group<Result>(RuntimeMaxCount)overload instead.
Resultis the result type of the awaitables that will be forked.
-
template<size_t MaxCount = 0, typename Awaitable, typename Result = tmc::detail::awaitable_result_t<Awaitable>>
aw_fork_group<MaxCount, Result> tmc::fork_group(Awaitable &&Aw)# Constructs a fork group with the first awaitable, deducing the result type from the awaitable’s return type.
MaxCountis the maximum number of awaitables that will be forked.If
Resultis void, thenMaxCountmust be 0. This allows you to fork an unlimited number of void-returning awaitables.If
Resultis non-void andMaxCountis non-zero, a fixed-size result array is used. If less thanMaxCountawaitables are forked, the remaining results will be default-initialized.If
Resultis non-void andMaxCountis 0, you must use thefork_group<Result>(RuntimeMaxCount)overload instead.
-
template<typename Result>
aw_fork_group<0, Result> tmc::fork_group(size_t RuntimeMaxCount)# Constructs an empty fork group with runtime-specified maximum size.
Resultis the result type of the awaitables that will be forked.Resultmust be non-void.RuntimeMaxCountis the maximum number of awaitables that may be forked. Astd::vectorof this size will be pre-allocated to store the results. If less thanRuntimeMaxCountawaitables are forked, the remaining results will be default-initialized.
-
template<typename Awaitable, typename Result = tmc::detail::awaitable_result_t<Awaitable>>
aw_fork_group<0, Result> tmc::fork_group(size_t RuntimeMaxCount, Awaitable &&Aw)# Constructs a fork group with runtime-specified maximum size and the first awaitable, deducing the result type from the awaitable. The deduced result type must be non-void.
RuntimeMaxCountis the maximum number of awaitables that may be forked. Astd::vectorof this size will be pre-allocated to store the results. If less thanRuntimeMaxCountawaitables are forked, the remaining results will be default-initialized.
-
template<size_t MaxCount, typename Result>
class aw_fork_group : public tmc::detail::resume_on_mixin<aw_fork_group<MaxCount, Result>># A container for imperatively forking awaitables, initiating each awaitable immediately, and joining them all at a later time.
Resultis the result type of the awaitables that will be forked. Different types of awaitables may be forked with a single fork_group, as long as they all return the same Result type.MaxCountdetermines the result storage strategy:If
Resultis void, thenMaxCountmust be 0. No storage is needed, so an unlimited number of awaitables can be forked.If
Resultis non-void andMaxCountis non-zero, a fixed-sizestd::array<Result, MaxCount>is used. You can fork up to MaxCount awaitables. If less than MaxCount awaitables are forked, the remaining results in the result array are default-initialized.If
Resultis non-void andMaxCountis zero, astd::vector<Result>is used. The size of this vector is fixed at construction time by passing theRuntimeMaxCountparameter. You can fork up to RuntimeMaxCount awaitables. If less than RuntimeMaxCount awaitables are forked, the remaining results in the result vector are default-initialized.
Public Functions
-
inline aw_fork_group()#
Constructs an empty fork group. It is recommended to use
tmc::fork_group()instead of this constructor.
-
inline aw_fork_group(size_t RuntimeMaxCount)#
Constructs an empty fork group with runtime size. It is recommended to use
tmc::fork_group(RuntimeMaxCount)instead of this constructor.
-
template<typename Awaitable>
inline aw_fork_group(Awaitable &&Aw)# Constructs a fork group with the first awaitable. It is recommended to use
tmc::fork_group(Awaitable)instead of this constructor.
-
template<typename Awaitable>
inline aw_fork_group(size_t RuntimeMaxCount, Awaitable &&Aw)# Constructs a fork group with runtime size and the first awaitable. It is recommended to use
tmc::fork_group(RuntimeMaxCount, Awaitable)instead of this constructor.
-
template<typename Awaitable, typename Exec = tmc::ex_any*>
inline void fork(Awaitable &&Aw, Exec &&Executor = tmc::current_executor(), size_t Priority = tmc::current_priority())# Initiates Awaitable immediately on the specified executor and priority.
Awaitablecan be any awaitable type as long as its result type matches the fork_group’s Result type.Executordefaults to the current executor.Prioritydefaults to the current priority.
-
template<typename Awaitable, typename Exec = tmc::ex_any*>
inline aw_fork_group_fork_clang fork_clang(Awaitable &&Aw, Exec &&Executor = tmc::current_executor(), size_t Priority = tmc::current_priority())# Similar to
fork()but allows the forked task’s allocation to be elided by combining it into the parent’s allocation (HALO). This works by using specific attributes that are only available on Clang 20+. You can safely call this function on other compilers, but no HALO-specific optimizations will be applied.WARNING: Don’t allow coroutines passed into this to cross a loop boundary, or Clang will try to reuse the same allocation for multiple active coroutines.
// the following usage will make your program CRASH! auto fg = tmc::fork_group(); for (int i = 0; i < 2; i++) { co_await fg.fork_clang(task(i)); } co_await std::move(fg);IMPORTANT: This returns a dummy awaitable. For HALO to work, you should not store the dummy awaitable. Instead,
co_awaitthis expression immediately. Proper usage:// note that this is the same as the prior example // but without the loop, it works fine auto fg = tmc::fork_group(); co_await fg.fork_clang(task(0)); co_await fg.fork_clang(task(1)); co_await std::move(fg);
-
inline bool await_ready() const noexcept#
Always suspends.
-
inline std::coroutine_handle await_suspend(std::coroutine_handle<> Outer) noexcept#
Suspends the outer coroutine and waits for the forked awaitables to complete.
-
inline std::add_rvalue_reference_t<ResultArray> await_resume() noexcept#
Returns the result array.
-
inline void await_resume() noexcept
Does nothing.
-
inline void reset() noexcept#
After you
co_awaitthis group, you may callreset()to make it usable again. This allows you to accumulate andco_awaitanother group of awaitables.For fixed-size fork_groups (MaxCount != 0), this resets the task count and done count, allowing the same result storage to be reused.
For runtime-sized fork_groups (MaxCount == 0 with non-void Result), this overload cannot be used. Instead, use
reset(RuntimeMaxCount)to specify a new capacity.
-
inline void reset(size_t RuntimeMaxCount) noexcept#
After you
co_awaitthis group, you may callreset(RuntimeMaxCount)to make it usable again with a new capacity.Only available for runtime-sized fork_groups (MaxCount == 0 with non-void Result). The result vector is resized to the new RuntimeMaxCount.
-
inline size_t capacity() noexcept#
Returns the maximum capacity of the fork_group as determined by the MaxCount or RuntimeMaxCount parameters. Note that this function is only guaranteed to return a valid value before this is
co_awaited.If the capacity is unlimited, this will return
std::numeric_limits<size_t>::max(), i.e.static_cast<size_t>(-1).
-
class aw_fork_group_fork_clang : private tmc::detail::AwaitTagNoGroupAsIs#
This is a dummy awaitable. Don’t store this in a variable. For HALO to work, you must
co_await fg.fork_clang()immediately.