Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,16 @@ task<> long_running()

task<> example()
{
auto [index, result] = co_await when_any(
auto result = co_await when_any(
fetch_int(), // task<int>
fetch_string() // task<std::string>
);
// index indicates which task won (0 or 1)
// result.index() indicates which task won (0 or 1)
// result is std::variant<int, std::string>
}
----

The result is a pair containing the winner's index and a deduplicated variant of possible result types. When a winner is determined, stop is requested for all siblings. All tasks complete before `when_any` returns.
The result is a variant with one alternative per input task, preserving positional correspondence. Use `.index()` to determine the winner. When a winner is determined, stop is requested for all siblings. All tasks complete before `when_any` returns.

For detailed coverage including error handling, cancellation, and the vector overload, see Racing Tasks.

Expand Down Expand Up @@ -213,15 +213,15 @@ Use `when_any` to implement timeout with fallback:
----
task<Response> fetch_with_timeout(Request req)
{
auto [index, result] = co_await when_any(
auto result = co_await when_any(
fetch_data(req),
timeout_after<Response>(100ms)
);

if (index == 1)
if (result.index() == 1)
throw timeout_error{"Request timed out"};

co_return std::get<Response>(result);
co_return std::get<0>(result);
}
----

Expand Down
62 changes: 32 additions & 30 deletions doc/unlisted/coroutines-when-any.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,78 +41,80 @@ the first one completes:

task<void> race()
{
auto [index, result] = co_await when_any(
auto result = co_await when_any(
fetch_from_primary(),
fetch_from_backup()
);
// result.index() is 0 or 1
}
----

The return value is a `std::pair` containing two elements: `index` indicates which task completed first (0 for the first argument, 1 for the second), and `result` holds the winning task's return value in a variant.
The return value is a `std::variant` with one alternative per input task. Use `.index()` to determine which task completed first (0 for the first argument, 1 for the second). The active alternative holds the winning task's return value.

The winning task's result is returned immediately. All sibling tasks receive
a stop request and are allowed to complete before `when_any` returns.

== Return Value

`when_any` returns a `std::pair` containing the winner's index and result.
The variadic `when_any` returns a `std::variant` with positional correspondence to the input tasks.

=== Heterogeneous Tasks (Variadic)

When racing tasks with different return types, the result is a variant:

[source,cpp]
----
auto [index, result] = co_await when_any(
auto result = co_await when_any(
task_returning_int(), // task<int>
task_returning_string() // task<std::string>
);

if (index == 0)
std::cout << "Got int: " << std::get<int>(result) << "\n";
if (result.index() == 0)
std::cout << "Got int: " << std::get<0>(result) << "\n";
else
std::cout << "Got string: " << std::get<std::string>(result) << "\n";
std::cout << "Got string: " << std::get<1>(result) << "\n";
----

The `result` variable is a `std::variant<int, std::string>`. Use `index` to determine which alternative is active, then extract the value with `std::get`.
The `result` variable is a `std::variant<int, std::string>`. Use `.index()` to determine which alternative is active, then extract the value with `std::get<N>`.

=== Void Tasks

Void tasks contribute `std::monostate` to the variant:

[source,cpp]
----
auto [index, result] = co_await when_any(
auto result = co_await when_any(
task_returning_int(), // task<int>
task_void() // task<void>
);

if (index == 0)
std::cout << "Got int: " << std::get<int>(result) << "\n";
if (result.index() == 0)
std::cout << "Got int: " << std::get<0>(result) << "\n";
else
std::cout << "Void task completed\n";
----

Tasks returning `void` contribute `std::monostate` to the variant. In this example, `result` has type `std::variant<int, std::monostate>`. When the void task wins, check for `std::monostate` or use the index to detect it.
Tasks returning `void` contribute `std::monostate` to the variant. In this example, `result` has type `std::variant<int, std::monostate>`. Use `.index()` to detect which task won.

=== Duplicate Types
=== Same-Type Tasks

The variant is deduplicated. When racing tasks with the same return type,
use the index to identify which task won:
The variant preserves one alternative per task.
Use `.index()` to identify which task won:

[source,cpp]
----
auto [index, result] = co_await when_any(
auto result = co_await when_any(
fetch_from_server_a(), // task<Response>
fetch_from_server_b(), // task<Response>
fetch_from_server_c() // task<Response>
);

auto response = std::get<Response>(result);
auto index = result.index();
auto response = std::visit([](auto&& v) -> Response { return v; }, result);
std::cout << "Server " << index << " responded first\n";
----

When multiple tasks share the same return type, the variant is deduplicated to contain only unique types. Here, `result` is `std::variant<Response>` with a single alternative. The `index` value (0, 1, or 2) tells you which server responded first.
When multiple tasks share the same return type, the variant contains one alternative per task. Here, `result` is `std::variant<Response, Response, Response>`. The `.index()` value (0, 1, or 2) tells you which server responded first. Use `std::visit` to extract the value.

=== Homogeneous Tasks (Vector)

Expand Down Expand Up @@ -153,7 +155,7 @@ that exception is rethrown from `when_any`:
task<void> handle_errors()
{
try {
auto [index, result] = co_await when_any(
auto result = co_await when_any(
might_fail(),
might_succeed()
);
Expand Down Expand Up @@ -194,7 +196,7 @@ task<Response> fetch_with_cancel_support()
task<void> example()
{
// When one fetch wins, the other sees stop_requested
auto [index, response] = co_await when_any(
auto response = co_await when_any(
fetch_with_cancel_support(),
fetch_with_cancel_support()
);
Expand All @@ -213,7 +215,7 @@ cancelled, all children see the request:
----
task<void> parent()
{
auto [index, result] = co_await when_any(
auto result = co_await when_any(
child_a(), // Sees parent's stop token
child_b() // Sees parent's stop token
);
Expand All @@ -234,7 +236,7 @@ All child tasks inherit the parent's executor affinity:
----
task<void> parent() // Running on executor ex
{
auto [index, result] = co_await when_any(
auto result = co_await when_any(
child_a(), // Runs on ex
child_b() // Runs on ex
);
Expand Down Expand Up @@ -267,14 +269,14 @@ Race requests to multiple servers for reliability:
----
task<Response> fetch_with_redundancy(Request req)
{
auto [index, response] = co_await when_any(
auto result = co_await when_any(
fetch_from(primary_server, req),
fetch_from(backup_server, req)
);

std::cout << (index == 0 ? "Primary" : "Backup")
std::cout << (result.index() == 0 ? "Primary" : "Backup")
<< " server responded\n";
co_return std::get<Response>(response);
co_return std::visit([](auto&& v) -> Response { return v; }, result);
}
----

Expand All @@ -286,15 +288,15 @@ Race an operation against a timer:
----
task<Data> fetch_with_timeout(Request req)
{
auto [index, result] = co_await when_any(
auto result = co_await when_any(
fetch_data(req),
timeout_after<Data>(100ms)
);

if (index == 1)
if (result.index() == 1)
throw timeout_error{"Request timed out"};

co_return std::get<Data>(result);
co_return std::get<0>(result);
}

// Helper that waits then throws
Expand Down Expand Up @@ -341,7 +343,7 @@ This function creates an acquire task for each pool, then races them. Whichever

| Return type
| Tuple of results
| Pair of (index, variant/value)
| Variant (variadic) or pair (range)

| Error handling
| First exception wins, siblings get stop
Expand All @@ -365,7 +367,7 @@ This function creates an acquire task for each pool, then races them. Whichever
| Race homogeneous tasks from a vector

| Return type (variadic)
| `pair<size_t, variant<...>>` with deduplicated types
| `variant<...>` with positional alternatives (use `.index()` for winner)

| Return type (vector)
| `pair<size_t, T>` or `size_t` for void
Expand Down
14 changes: 8 additions & 6 deletions example/when-any-cancellation/when_any_cancellation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ task<> race_data_sources()
// source_a: 2 steps * 20ms = fast
// source_b: 5 steps * 20ms = medium
// source_c: 8 steps * 20ms = slow
auto [winner_index, result] = co_await when_any(
auto result = co_await when_any(
fetch_from_source("source_a", 2, 20),
fetch_from_source("source_b", 5, 20),
fetch_from_source("source_c", 8, 20));

auto value = std::get<std::string>(result);
auto winner_index = result.index();
// All alternatives are std::string, use std::visit to extract
auto value = std::visit([](auto&& v) -> std::string { return v; }, result);
std::cout << "\nWinner: index=" << winner_index
<< " value=\"" << value << "\"\n";
}
Expand Down Expand Up @@ -108,17 +110,17 @@ task<> timeout(int ms)
}

// Use when_any with a timeout to bound the lifetime of a background worker.
// With void tasks, the variadic overload returns pair<size_t, variant<monostate>>.
// We only need the winner index to know which task completed first.
// With void tasks, the variadic overload returns variant<monostate, monostate>.
// Use .index() to know which task completed first.
task<> timeout_a_worker()
{
std::cout << "\n=== Timeout a background worker ===\n\n";

auto [winner, _] = co_await when_any(
auto result = co_await when_any(
background_worker("worker", 30),
timeout(100));

if (winner == 1)
if (result.index() == 1)
std::cout << "\nTimeout fired — worker was cancelled\n";
else
std::cout << "\nWorker finished before timeout\n";
Expand Down
Loading
Loading