.. _spawn_group:

tmc::spawn_group()
-----------------------------------------------------------------------------------
``spawn_group()`` provides an imperative interface for building a group of awaitables of the same type. Similar to ``spawn_many()``, it allows you to customize execution behavior and wait for all awaitables to complete, but awaitables are collected incrementally rather than provided all at once.
The wrapped awaitables will be executed concurrently.

Unlike :literal_ref:`fork_group<fork_group>`, ``spawn_group`` is lazy - awaitables are not initiated until you ``co_await`` the group. This also means ``spawn_group`` is movable, so it can be returned from functions or passed around.

Key Differences
------------------------------------------------------------------------------------------------

**spawn_group vs fork_group:**

* ``spawn_group`` is lazy - awaitables are collected but not initiated until ``co_await``
* ``spawn_group`` is movable - can be returned from functions or stored
* ``spawn_group`` requires all awaitables to be the **same type**
* ``spawn_group`` supports ``run_on()``, ``resume_on()``, and ``with_priority()`` customizations

**spawn_group vs spawn_many:**

* ``spawn_group`` allows imperative construction - add awaitables one at a time
* ``spawn_group`` can do HALO (via ``add_clang()``)
* ``spawn_group`` can be reused (via ``reset()``)

Template Parameters
------------------------------------------------------------------------------------------------
``spawn_group()`` has two template parameters:

* ``MaxCount`` (default: 0): The maximum number of awaitables that may be added.
* ``Awaitable`` (default: ``tmc::task<void>``): The type of awaitables that will be added. All awaitables must be of this type.

**Storage Strategies:**

* If ``MaxCount`` is non-zero, a fixed-size ``std::array<Awaitable, MaxCount>`` is used.
* If ``MaxCount`` is 0, a ``std::vector<Awaitable>`` is used, allowing an unlimited number of awaitables.

Result Storage
------------------------------------------------------------------------------------------------
Results are returned the same way as :literal_ref:`spawn_many()<spawn_many>`:

* ``void`` if the awaitable result type is ``void``
* ``std::array<Result, MaxCount>`` for fixed-size groups
* ``std::vector<Result>`` for dynamic-size groups

If the result type is not default-constructible, each value will be wrapped into ``std::optional<Result>``.

Imperative Add Interface
------------------------------------------------------------------------------------------------

``.add()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Adds an awaitable to the group. The awaitable will be initiated when the group is co_awaited.

.. code-block:: cpp

   auto sg = tmc::spawn_group();
   sg.add(task_void());
   sg.add(another_task());
   co_await std::move(sg);

``.add_clang()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Similar to ``add()`` but allows the child task's allocation to be elided via HALO (Heap Allocation eLision Optimization) when compiled with Clang 20+. See :ref:`HALO<halo>` for details.

**IMPORTANT:** You must ``co_await`` the result of ``add_clang()`` immediately for HALO to be possible.

.. code-block:: cpp

   auto sg = tmc::spawn_group();
   co_await sg.add_clang(task_void());
   co_await sg.add_clang(another_task());
   co_await std::move(sg);

**WARNING:** Do not use ``add_clang()`` in a loop, as Clang will try to reuse the same allocation for multiple active coroutines, causing crashes.

.. code-block:: cpp

   // WRONG - Will crash!
   auto sg = tmc::spawn_group();
   for (int i = 0; i < 2; i++) {
       co_await sg.add_clang(task(i));
   }
   co_await std::move(sg);

   // The equivalent without the loop works fine.
   auto sg = tmc::spawn_group();
   co_await sg.add_clang(task(0));
   co_await sg.add_clang(task(1));
   co_await std::move(sg);

   // Or you can use regular add() in a loop
   // (but HALO will not be performed)
   auto sg = tmc::spawn_group();
   for (int i = 0; i < 2; i++) {
       sg.add(task(i));
   }
   co_await std::move(sg);

Awaitable Customizations
------------------------------------------------------------------------------------------------
The ``spawn_group`` awaitable supports these :ref:`Awaitable Customizations <awaitable_customizations>`:
:literal_ref:`run_on()<run_on>`,
:literal_ref:`resume_on()<resume_on>`,
:literal_ref:`with_priority()<with_priority>`,
:literal_ref:`co_await<co_await>`,
:literal_ref:`fork()<fork>`

Unlike ``fork_group``, ``spawn_group`` requires you to customize the executor and priority for all tasks in the group at once:

.. code-block:: cpp

   auto sg = tmc::spawn_group();
   sg.add(task1());
   sg.add(task2());
   co_await std::move(sg).run_on(tmc::cpu_executor()).with_priority(1);

   // Or fork them
   auto forked = std::move(sg).run_on(tmc::cpu_executor()).fork();
   do_some_work();
   co_await std::move(forked);

Movability
------------------------------------------------------------------------------------------------
Unlike ``fork_group``, ``spawn_group`` is movable. This makes it useful for building task groups in helper functions:

.. code-block:: cpp

   auto build_tasks() -> tmc::aw_spawn_group<0, tmc::task<int>> {
       auto sg = tmc::spawn_group<0, tmc::task<int>>();
       sg.add(compute_task(1));
       sg.add(compute_task(2));
       return sg; // OK - spawn_group is movable
   }

   tmc::task<void> example() {
       auto sg = build_tasks();
       auto results = co_await std::move(sg);
   }

API Reference
------------------------------------------------------------------------------------------------

.. doxygenfunction:: tmc::spawn_group()

.. doxygenfunction:: tmc::spawn_group(Awaitable&& Aw)

.. doxygenclass:: tmc::aw_spawn_group
   :members:
