boost::capy::when_any
Wait for the first awaitable to complete (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::pair<std::size_t, /* implementation-defined */>>
when_any(R&& awaitables);
Description
Races a range of awaitables with the same result type. Accepts any sized input range of IoAwaitable types, enabling use with arrays, spans, or custom containers.
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 and result are 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::array<task<Response>, 3> requests = {
fetch_from_server(0),
fetch_from_server(1),
fetch_from_server(2)
};
auto [index, response] = co_await when_any(std::move(requests));
}
Example with Vector
task<Response> fetch_fastest(std::vector<Server> const& servers) {
std::vector<task<Response>> requests;
for (auto const& server : servers)
requests.push_back(fetch_from(server));
auto [index, response] = co_await when_any(std::move(requests));
co_return response;
}
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 variadic overload, no variant wrapper is needed since all tasks share the same return type.
Exceptions
Name |
Thrown on |
|
if range is empty (thrown before coroutine suspends). |
|
the winner's exception if the winning task threw an exception. |
Parameters
| Name | Description |
|---|---|
awaitables |
Range of awaitables to race concurrently (must not be empty). |
See Also
when_any, IoAwaitableRange
Created with MrDocs