boost::capy::when_any

Wait for the first awaitable to complete (void range overload).

Synopsis

Declared in <boost/capy/when_any.hpp>

template<IoAwaitableRange R>
requires std::is_void_v<detail::awaitable_result_t<std::ranges::range_value_t<R>>>
[[nodiscard]]
task<std::size_t>
when_any(R&& awaitables);

Description

Races a range of void‐returning awaitables. Since void awaitables have no result value, only the winner's index is returned.

Suspends

The calling coroutine suspends when co_await is invoked. All awaitables in the range are launched concurrently and execute in parallel. The coroutine resumes only after all awaitables have completed, even though the winner is determined by the first to finish.

Completion Conditions

  • Winner is determined when the first awaitable completes (success or exception)

  • Only one task can claim winner status via atomic compare‐exchange

  • Once a winner exists, stop is requested for all remaining siblings

  • Parent coroutine resumes only after all siblings acknowledge completion

  • The winner's index is returned; if the winner threw, the exception is rethrown

Cancellation Semantics

Cancellation is supported via stop_token propagated through the IoAwaitable protocol:

  • Each child awaitable receives a stop_token derived from a shared stop_source

  • When the parent's stop token is activated, the stop is forwarded to all children

  • When a winner is determined, stop_source_.request_stop() is called immediately

  • Siblings must handle cancellation gracefully and complete before parent resumes

  • Stop requests are cooperative; tasks must check and respond to them

Concurrency/Overlap

All awaitables are launched concurrently before any can complete. The launcher iterates through the range, starting each task on the caller's executor. Tasks may execute in parallel on multi‐threaded executors or interleave on single‐threaded executors. There is no guaranteed ordering of task completion.

Notable Error Conditions

  • Empty range: throws std::invalid_argument immediately (not via co_return)

  • Winner exception: if the winning task threw, that exception is rethrown

  • Non‐winner exceptions: silently discarded (only winner's result matters)

  • Cancellation: tasks may complete via cancellation without throwing

Example

task<void> example() {
    std::vector<task<void>> tasks;
    for (int i = 0; i < 5; ++i)
        tasks.push_back(background_work(i));

    std::size_t winner = co_await when_any(std::move(tasks));
    // winner is the index of the first task to complete
}

Example with Timeout

task<void> with_timeout() {
    std::vector<task<void>> tasks;
    tasks.push_back(long_running_operation());
    tasks.push_back(delay(std::chrono::seconds(5)));

    std::size_t winner = co_await when_any(std::move(tasks));
    if (winner == 1) {
        // Timeout occurred
    }
}

Remarks

Elements are moved from the range; for lvalue ranges, the original container will have moved‐from elements after this call. The range is moved onto the coroutine frame to ensure lifetime safety. Unlike the non‐void overload, no result storage is needed since void tasks produce no value.

Exceptions

Name

Thrown on

std::invalid_argument

if range is empty (thrown before coroutine suspends).

Rethrows

the winner's exception if the winning task threw an exception.

Return Value

A task yielding the winner's index (zero‐based).

Template Parameters

Name Description

R

Range type satisfying IoAwaitableRange with void result.

Parameters

Name Description

awaitables

Range of void awaitables to race concurrently (must not be empty).

See Also

when_any, IoAwaitableRange

Created with MrDocs