From 701828531537ca02df962f08bfee1168cffb561f Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Mon, 2 Mar 2026 12:07:52 -0700 Subject: [PATCH] Refactor variadic when_any to return positional variant Replace pair return type with a plain std::variant that preserves one alternative per input task. This makes variant::index() the single mechanism for identifying the winner and eliminates the type-deduplication machinery (unique_variant_t and friends). Internally, variant construction uses in_place_index instead of in_place_type to support same-type alternatives. The homogeneous range overloads (vector) are unchanged. --- .../pages/4.coroutines/4f.composition.adoc | 12 +- doc/unlisted/coroutines-when-any.adoc | 62 +++--- .../when_any_cancellation.cpp | 14 +- include/boost/capy/when_any.hpp | 160 +++++++-------- test/unit/when_any.cpp | 187 +++++++++--------- 5 files changed, 203 insertions(+), 232 deletions(-) diff --git a/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc b/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc index 23135588..48c48068 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc @@ -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 fetch_string() // task ); - // index indicates which task won (0 or 1) + // result.index() indicates which task won (0 or 1) // result is std::variant } ---- -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. @@ -213,15 +213,15 @@ Use `when_any` to implement timeout with fallback: ---- task fetch_with_timeout(Request req) { - auto [index, result] = co_await when_any( + auto result = co_await when_any( fetch_data(req), timeout_after(100ms) ); - if (index == 1) + if (result.index() == 1) throw timeout_error{"Request timed out"}; - co_return std::get(result); + co_return std::get<0>(result); } ---- diff --git a/doc/unlisted/coroutines-when-any.adoc b/doc/unlisted/coroutines-when-any.adoc index 45348b0a..0ecb560e 100644 --- a/doc/unlisted/coroutines-when-any.adoc +++ b/doc/unlisted/coroutines-when-any.adoc @@ -41,21 +41,22 @@ the first one completes: task 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) @@ -63,18 +64,18 @@ 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 task_returning_string() // task ); -if (index == 0) - std::cout << "Got int: " << std::get(result) << "\n"; +if (result.index() == 0) + std::cout << "Got int: " << std::get<0>(result) << "\n"; else - std::cout << "Got string: " << std::get(result) << "\n"; + std::cout << "Got string: " << std::get<1>(result) << "\n"; ---- -The `result` variable is a `std::variant`. Use `index` to determine which alternative is active, then extract the value with `std::get`. +The `result` variable is a `std::variant`. Use `.index()` to determine which alternative is active, then extract the value with `std::get`. === Void Tasks @@ -82,37 +83,38 @@ 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 task_void() // task ); -if (index == 0) - std::cout << "Got int: " << std::get(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`. 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`. 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 fetch_from_server_b(), // task fetch_from_server_c() // task ); -auto response = std::get(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` 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`. The `.index()` value (0, 1, or 2) tells you which server responded first. Use `std::visit` to extract the value. === Homogeneous Tasks (Vector) @@ -153,7 +155,7 @@ that exception is rethrown from `when_any`: task handle_errors() { try { - auto [index, result] = co_await when_any( + auto result = co_await when_any( might_fail(), might_succeed() ); @@ -194,7 +196,7 @@ task fetch_with_cancel_support() task 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() ); @@ -213,7 +215,7 @@ cancelled, all children see the request: ---- task 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 ); @@ -234,7 +236,7 @@ All child tasks inherit the parent's executor affinity: ---- task 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 ); @@ -267,14 +269,14 @@ Race requests to multiple servers for reliability: ---- task 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); + co_return std::visit([](auto&& v) -> Response { return v; }, result); } ---- @@ -286,15 +288,15 @@ Race an operation against a timer: ---- task fetch_with_timeout(Request req) { - auto [index, result] = co_await when_any( + auto result = co_await when_any( fetch_data(req), timeout_after(100ms) ); - if (index == 1) + if (result.index() == 1) throw timeout_error{"Request timed out"}; - co_return std::get(result); + co_return std::get<0>(result); } // Helper that waits then throws @@ -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 @@ -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>` with deduplicated types +| `variant<...>` with positional alternatives (use `.index()` for winner) | Return type (vector) | `pair` or `size_t` for void diff --git a/example/when-any-cancellation/when_any_cancellation.cpp b/example/when-any-cancellation/when_any_cancellation.cpp index f11ede75..b2869072 100644 --- a/example/when-any-cancellation/when_any_cancellation.cpp +++ b/example/when-any-cancellation/when_any_cancellation.cpp @@ -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(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"; } @@ -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>. -// We only need the winner index to know which task completed first. +// With void tasks, the variadic overload returns variant. +// 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"; diff --git a/include/boost/capy/when_any.hpp b/include/boost/capy/when_any.hpp index de5f961b..14cfbc0f 100644 --- a/include/boost/capy/when_any.hpp +++ b/include/boost/capy/when_any.hpp @@ -62,24 +62,21 @@ 3. Stop is requested immediately when winner is determined 4. Only the winner's result/exception is stored - TYPE DEDUPLICATION: + POSITIONAL VARIANT: ------------------- - std::variant requires unique alternative types. Since when_any can race - tasks with identical return types (e.g., three task), we must - deduplicate types before constructing the variant. + The variadic overload returns a std::variant with one alternative per + input task, preserving positional correspondence. Use .index() on + the variant to identify which task won. Example: when_any(task, task, task) - Raw types after void->monostate: int, string, int - - Deduplicated variant: std::variant - - Return: pair> - - The winner_index tells you which task won (0, 1, or 2), while the variant - holds the result. Use the index to determine how to interpret the variant. + - Result variant: std::variant + - variant.index() tells you which task won (0, 1, or 2) VOID HANDLING: -------------- - void tasks contribute std::monostate to the variant (then deduplicated). - All-void tasks result in: pair> + void tasks contribute std::monostate to the variant. + All-void tasks result in: variant MEMORY MODEL: ------------- @@ -131,46 +128,11 @@ namespace detail { template using void_to_monostate_t = std::conditional_t, std::monostate, T>; -// Type deduplication: std::variant requires unique alternative types. -// Fold left over the type list, appending each type only if not already present. -template -struct variant_append_if_unique; - -template -struct variant_append_if_unique, T> -{ - using type = std::conditional_t< - (std::is_same_v || ...), - std::variant, - std::variant>; -}; - -template -struct deduplicate_impl; - -template -struct deduplicate_impl -{ - using type = Accumulated; -}; - -template -struct deduplicate_impl -{ - using next = typename variant_append_if_unique::type; - using type = typename deduplicate_impl::type; -}; - -// Deduplicated variant; void types become monostate before deduplication +// Result variant: one alternative per task, preserving positional +// correspondence. Use .index() to identify which task won. +// void results become monostate. template -using unique_variant_t = typename deduplicate_impl< - std::variant>, - void_to_monostate_t...>::type; - -// Result: (winner_index, deduplicated_variant). Use index to disambiguate -// when multiple tasks share the same return type. -template -using when_any_result_t = std::pair>; +using when_any_variant_t = std::variant, void_to_monostate_t...>; /** Core shared state for when_any operations. @@ -247,7 +209,7 @@ template struct when_any_state { static constexpr std::size_t task_count = 1 + sizeof...(Ts); - using variant_type = unique_variant_t; + using variant_type = when_any_variant_t; when_any_core core_; std::optional result_; @@ -261,19 +223,20 @@ struct when_any_state // Runners self-destruct in final_suspend. No destruction needed here. /** @pre core_.try_win() returned true. - @note Uses in_place_type (not index) because variant is deduplicated. + @note Uses in_place_index (not type) for positional variant access. */ - template + template void set_winner_result(T value) noexcept(std::is_nothrow_move_constructible_v) { - result_.emplace(std::in_place_type, std::move(value)); + result_.emplace(std::in_place_index, std::move(value)); } /** @pre core_.try_win() returned true. */ + template void set_winner_void() noexcept { - result_.emplace(std::in_place_type, std::monostate{}); + result_.emplace(std::in_place_index, std::monostate{}); } }; @@ -402,7 +365,40 @@ struct when_any_runner } }; -/** Wraps a child awaitable, attempts to claim winner on completion. +/** Indexed overload for heterogeneous when_any (compile-time index). + + Uses compile-time index I for variant construction via in_place_index. + Called from when_any_launcher::launch_one(). +*/ +template +when_any_runner +make_when_any_runner(Awaitable inner, StateType* state) +{ + using T = awaitable_result_t; + if constexpr (std::is_void_v) + { + co_await std::move(inner); + if(state->core_.try_win(I)) + state->template set_winner_void(); + } + else + { + auto result = co_await std::move(inner); + if(state->core_.try_win(I)) + { + try + { + state->template set_winner_result(std::move(result)); + } + catch(...) + { + state->core_.set_winner_exception(std::current_exception()); + } + } + } +} + +/** Runtime-index overload for homogeneous when_any (range path). Uses requires-expressions to detect state capabilities: - set_winner_void(): for heterogeneous void tasks (stores monostate) @@ -419,10 +415,8 @@ make_when_any_runner(Awaitable inner, StateType* state, std::size_t index) co_await std::move(inner); if(state->core_.try_win(index)) { - // Heterogeneous void tasks store monostate in the variant if constexpr (requires { state->set_winner_void(); }) state->set_winner_void(); - // Homogeneous void tasks have no result to store } } else @@ -430,8 +424,6 @@ make_when_any_runner(Awaitable inner, StateType* state, std::size_t index) auto result = co_await std::move(inner); if(state->core_.try_win(index)) { - // Defensive: move should not throw (already moved once), but we - // catch just in case since an uncaught exception would be devastating. try { state->set_winner_result(std::move(result)); @@ -503,8 +495,8 @@ class when_any_launcher template void launch_one(executor_ref caller_ex, std::stop_token token) { - auto runner = make_when_any_runner( - std::move(std::get(*tasks_)), state_, I); + auto runner = make_when_any_runner( + std::move(std::get(*tasks_)), state_); auto h = runner.release(); h.promise().state_ = state_; @@ -522,8 +514,8 @@ class when_any_launcher /** Wait for the first awaitable to complete. Races multiple heterogeneous awaitables concurrently and returns when the - first one completes. The result includes the winner's index and a - deduplicated variant containing the result value. + first one completes. The result is a variant with one alternative per + input task, preserving positional correspondence. @par Suspends The calling coroutine suspends when co_await is invoked. All awaitables @@ -562,26 +554,15 @@ class when_any_launcher @par Example @code task example() { - auto [index, result] = co_await when_any( - fetch_from_primary(), // task - fetch_from_backup() // task - ); - // index is 0 or 1, result holds the winner's Response - auto response = std::get(result); - } - @endcode - - @par Example with Heterogeneous Types - @code - task mixed_types() { - auto [index, result] = co_await when_any( + auto result = co_await when_any( fetch_int(), // task fetch_string() // task ); - if (index == 0) - std::cout << "Got int: " << std::get(result) << "\n"; + // result.index() is 0 or 1 + if (result.index() == 0) + std::cout << "Got int: " << std::get<0>(result) << "\n"; else - std::cout << "Got string: " << std::get(result) << "\n"; + std::cout << "Got string: " << std::get<1>(result) << "\n"; } @endcode @@ -589,29 +570,26 @@ class when_any_launcher @tparam As Remaining awaitable types (must satisfy IoAwaitable). @param a0 The first awaitable to race. @param as Additional awaitables to race concurrently. - @return A task yielding a pair of (winner_index, result_variant). + @return A task yielding a variant with one alternative per awaitable. + Use .index() to identify the winner. Void awaitables contribute + std::monostate. @throws Rethrows the winner's exception if the winning task threw an exception. @par Remarks Awaitables are moved into the coroutine frame; original objects become - empty after the call. When multiple awaitables share the same return type, - the variant is deduplicated to contain only unique types. Use the winner - index to determine which awaitable completed first. Void awaitables - contribute std::monostate to the variant. + empty after the call. The variant preserves one alternative per input + task. Use .index() to determine which awaitable completed first. + Void awaitables contribute std::monostate to the variant. @see when_all, IoAwaitable */ template [[nodiscard]] auto when_any(A0 a0, As... as) - -> task task, detail::awaitable_result_t...>> { - using result_type = detail::when_any_result_t< - detail::awaitable_result_t, - detail::awaitable_result_t...>; - detail::when_any_state< detail::awaitable_result_t, detail::awaitable_result_t...> state; @@ -622,7 +600,7 @@ template if(state.core_.winner_exception_) std::rethrow_exception(state.core_.winner_exception_); - co_return result_type{state.core_.winner_index_, std::move(*state.result_)}; + co_return std::move(*state.result_); } /** Concept for ranges of full I/O awaitables. diff --git a/test/unit/when_any.cpp b/test/unit/when_any.cpp index 7915da91..c815b3d4 100644 --- a/test/unit/when_any.cpp +++ b/test/unit/when_any.cpp @@ -49,8 +49,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result = std::get<0>(r.second); + winner_index = r.index(); + result = std::get<0>(r); }, [](std::exception_ptr) {})( when_any(returns_int(42))); @@ -73,9 +73,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - // Variant is deduplicated to single int type - result_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result_value = v; }, r); }, [](std::exception_ptr) {})( when_any(returns_int(10), returns_int(20))); @@ -97,25 +96,22 @@ struct when_any_test test_executor ex(dispatch_count); bool completed = false; std::size_t winner_index = 999; - std::variant result_value; - + // variant — one alternative per task run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = r.second; + winner_index = r.index(); + switch (r.index()) { + case 0: BOOST_TEST_EQ(std::get<0>(r), 1); break; + case 1: BOOST_TEST_EQ(std::get<1>(r), "hello"); break; + case 2: BOOST_TEST_EQ(std::get<2>(r), 3); break; + } }, [](std::exception_ptr) {})( when_any(returns_int(1), returns_string("hello"), returns_int(3))); BOOST_TEST(completed); BOOST_TEST(winner_index == 0 || winner_index == 1 || winner_index == 2); - if (winner_index == 0) - BOOST_TEST_EQ(std::get(result_value), 1); - else if (winner_index == 1) - BOOST_TEST_EQ(std::get(result_value), "hello"); - else - BOOST_TEST_EQ(std::get(result_value), 3); } // Test: Void task can win @@ -126,23 +122,18 @@ struct when_any_test test_executor ex(dispatch_count); bool completed = false; std::size_t winner_index = 999; - std::variant result_value; - run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = r.second; + winner_index = r.index(); + if (r.index() == 1) + BOOST_TEST_EQ(std::get<1>(r), 42); }, [](std::exception_ptr) {})( when_any(void_task(), returns_int(42))); BOOST_TEST(completed); BOOST_TEST(winner_index == 0 || winner_index == 1); - if (winner_index == 0) - BOOST_TEST(std::holds_alternative(result_value)); - else - BOOST_TEST_EQ(std::get(result_value), 42); } // Test: All void tasks @@ -153,21 +144,16 @@ struct when_any_test test_executor ex(dispatch_count); bool completed = false; std::size_t winner_index = 999; - std::variant result_value; - run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = r.second; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(void_task(), void_task(), void_task())); BOOST_TEST(completed); BOOST_TEST(winner_index == 0 || winner_index == 1 || winner_index == 2); - // All void tasks produce monostate regardless of index - BOOST_TEST(std::holds_alternative(result_value)); } //---------------------------------------------------------- @@ -329,7 +315,7 @@ struct when_any_test [&](auto&& r) { completed = true; // Winner should be first task (synchronous executor) - BOOST_TEST_EQ(r.first, 0u); + BOOST_TEST_EQ(r.index(), 0u); }, [](std::exception_ptr) {})( when_any( @@ -383,8 +369,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { when_any_completed = true; - winner_index = r.first; - winner_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { winner_value = v; }, r); }, [](std::exception_ptr) {})( when_any(fast_task(), slow_task(100, 10), slow_task(200, 10))); @@ -440,8 +426,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { when_any_completed = true; - winner_index = r.first; - winner_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { winner_value = v; }, r); }, [](std::exception_ptr) {})( when_any(medium_task(10, 3), medium_task(20, 1), medium_task(30, 4))); @@ -493,7 +479,7 @@ struct when_any_test run_async(ex, [&](auto&& r) { when_any_completed = true; - BOOST_TEST_EQ(r.first, 0u); // fast_task wins + BOOST_TEST_EQ(r.index(), 0u); // fast_task wins }, [](std::exception_ptr) {})( when_any(fast_task(), non_cooperative_task(100, 3), non_cooperative_task(200, 3))); @@ -551,7 +537,7 @@ struct when_any_test run_async(ex, [&](auto&& r) { when_any_completed = true; - BOOST_TEST_EQ(r.first, 0u); + BOOST_TEST_EQ(r.index(), 0u); }, [](std::exception_ptr) {})( when_any(fast_task(), cooperative_slow(5), non_cooperative_slow(5))); @@ -582,13 +568,13 @@ struct when_any_test int result = 0; auto inner1 = []() -> task { - auto [idx, res] = co_await when_any(returns_int(10), returns_int(20)); - co_return std::get(res); + auto res = co_await when_any(returns_int(10), returns_int(20)); + co_return std::visit([](auto v) { return v; }, res); }; auto inner2 = []() -> task { - auto [idx, res] = co_await when_any(returns_int(30), returns_int(40)); - co_return std::get(res); + auto res = co_await when_any(returns_int(30), returns_int(40)); + co_return std::visit([](auto v) { return v; }, res); }; std::size_t winner_index = 999; @@ -596,8 +582,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result = v; }, r); }, [](std::exception_ptr) {})( when_any(inner1(), inner2())); @@ -620,13 +606,13 @@ struct when_any_test bool completed = false; auto race1 = []() -> task { - auto [idx, res] = co_await when_any(returns_int(1), returns_int(2)); - co_return std::get(res); + auto res = co_await when_any(returns_int(1), returns_int(2)); + co_return std::visit([](auto v) { return v; }, res); }; auto race2 = []() -> task { - auto [idx, res] = co_await when_any(returns_int(3), returns_int(4)); - co_return std::get(res); + auto res = co_await when_any(returns_int(3), returns_int(4)); + co_return std::visit([](auto v) { return v; }, res); }; run_async(ex, @@ -665,8 +651,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result_value = v; }, r); }, [](std::exception_ptr) {})( when_any(concurrent1(), concurrent2())); @@ -697,8 +683,8 @@ struct when_any_test run_async(ex, [&](auto r) { completed = true; - winner_index = r.first; - result_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result_value = v; }, r); }, [](std::exception_ptr) {})(when_any( returns_int(1), returns_int(2), returns_int(3), returns_int(4), @@ -732,8 +718,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result_value = v; }, r); }, [](std::exception_ptr) {})( when_any(multi_step_task(10), multi_step_task(20))); @@ -767,8 +753,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result_value = v; }, r); }, [](std::exception_ptr) {})(std::move(awaitable2)); @@ -795,8 +781,8 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result_value = v; }, r); }, [](std::exception_ptr) {})(std::move(deferred)); @@ -820,14 +806,13 @@ struct when_any_test test_executor ex(dispatch_count); bool completed = false; - // Note: deduplicates to variant + // preserves all three alternatives run_async(ex, [&](auto&& r) { completed = true; // With synchronous executor, first task wins - BOOST_TEST_EQ(r.first, 0u); - BOOST_TEST(std::holds_alternative(r.second)); - BOOST_TEST_EQ(std::get(r.second), 42); + BOOST_TEST_EQ(r.index(), 0u); + BOOST_TEST_EQ(std::get<0>(r), 42); }, [](std::exception_ptr) {})( when_any(returns_int(42), returns_string("hello"), returns_int(99))); @@ -843,23 +828,23 @@ struct when_any_test test_executor ex(dispatch_count); bool completed = false; std::size_t winner_index = 999; - std::variant result_value; - run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = r.second; + winner_index = r.index(); + std::visit([&](auto const& v) { + using T = std::decay_t; + if constexpr (std::is_same_v) + BOOST_TEST_EQ(v, 42); + else + BOOST_TEST_EQ(v, "hello"); + }, r); }, [](std::exception_ptr) {})( when_any(returns_int(42), returns_string("hello"))); BOOST_TEST(completed); BOOST_TEST(winner_index == 0 || winner_index == 1); - if (winner_index == 0) - BOOST_TEST_EQ(std::get(result_value), 42); - else - BOOST_TEST_EQ(std::get(result_value), "hello"); } //---------------------------------------------------------- @@ -894,7 +879,7 @@ struct when_any_test run_async(ex, parent_stop.get_token(), [&](auto&& r) { when_any_completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(check_stop_task(1), check_stop_task(2), check_stop_task(3))); @@ -1043,7 +1028,7 @@ struct when_any_test run_async(ex, [&](auto&& r) { when_any_completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(fast_task(), nested_when_any_task())); @@ -1094,16 +1079,16 @@ struct when_any_test // A task containing a nested when_any - doesn't check stop first auto nested_when_any_task = [&]() -> task { // Start inner when_any immediately (no stop check first) - auto [idx, res] = co_await when_any( + auto res = co_await when_any( slow_inner_task(10), slow_inner_task(10)); - co_return std::get(res); + co_return std::visit([](auto v) { return v; }, res); }; run_async(ex, [&](auto&& r) { when_any_completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(yielding_fast_task(), nested_when_any_task())); @@ -1146,19 +1131,19 @@ struct when_any_test run_async(ex, [&](auto&& r) { completed = true; - // The correct pattern: use index to determine which type to access - switch (r.first) { + // The correct pattern: use .index() to determine which alternative + switch (r.index()) { case 0: - correct_access = std::holds_alternative(r.second); - BOOST_TEST_EQ(std::get(r.second), 42); + correct_access = true; + BOOST_TEST_EQ(std::get<0>(r), 42); break; case 1: - correct_access = std::holds_alternative(r.second); - BOOST_TEST_EQ(std::get(r.second), "hello"); + correct_access = true; + BOOST_TEST_EQ(std::get<1>(r), "hello"); break; case 2: - correct_access = std::holds_alternative(r.second); - BOOST_TEST_EQ(std::get(r.second), 3.14); + correct_access = true; + BOOST_TEST_EQ(std::get<2>(r), 3.14); break; } }, @@ -1169,9 +1154,9 @@ struct when_any_test BOOST_TEST(correct_access); } - // Test: Variant with duplicate types - index disambiguation + // Test: Same-type tasks — use .index() to identify the winner void - testVariantDuplicateTypesIndexDisambiguation() + testVariantSameTypeIndexDisambiguation() { int dispatch_count = 0; test_executor ex(dispatch_count); @@ -1179,13 +1164,13 @@ struct when_any_test std::size_t winner_index = 999; int result_value = 0; - // when_any(int, int, int) deduplicates to variant - // but winner_index tells us WHICH task won + // when_any(int, int, int) produces variant + // .index() tells us WHICH task won run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result_value = std::get(r.second); + winner_index = r.index(); + std::visit([&](auto v) { result_value = v; }, r); }, [](std::exception_ptr) {})( when_any(returns_int(100), returns_int(200), returns_int(300))); @@ -1250,7 +1235,7 @@ struct when_any_test testVariantAlternativePopulated(); testVariantVisit(); testVariantAccessByIndex(); - testVariantDuplicateTypesIndexDisambiguation(); + testVariantSameTypeIndexDisambiguation(); } }; @@ -1673,8 +1658,8 @@ struct when_any_vector_test std::size_t outer_winner = 999; auto variadic_race = []() -> task { - auto [idx, res] = co_await when_any(returns_int(1), returns_int(2)); - co_return std::get(res); + auto res = co_await when_any(returns_int(1), returns_int(2)); + co_return std::visit([](auto v) { return v; }, res); }; auto vector_race = []() -> task { @@ -1688,8 +1673,8 @@ struct when_any_vector_test run_async(ex, [&](auto r) { completed = true; - outer_winner = r.first; - auto result = std::get(r.second); + outer_winner = r.index(); + int result = std::visit([](auto v) { return v; }, r); if (outer_winner == 0) BOOST_TEST((result == 1 || result == 2)); else @@ -1755,7 +1740,7 @@ struct when_any_io_awaitable_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(stop_only_awaitable{}, returns_int(42))); @@ -1785,7 +1770,7 @@ struct when_any_io_awaitable_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(event.wait(), returns_int(42))); @@ -1816,7 +1801,7 @@ struct when_any_io_awaitable_test run_async(ex, parent_stop.get_token(), [&](auto&& r) { completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(stop_only_awaitable{}, stop_only_awaitable{})); @@ -1850,7 +1835,7 @@ struct when_any_io_awaitable_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(io_op(), io_op())); @@ -1876,8 +1861,12 @@ struct when_any_io_awaitable_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; - result = std::get>(r.second); + winner_index = r.index(); + std::visit([&](auto const& v) { + using T = std::decay_t; + if constexpr (std::is_same_v>) + result = v; + }, r); }, [](std::exception_ptr) {})( when_any(io_read(100), io_read(200))); @@ -1906,7 +1895,7 @@ struct when_any_io_awaitable_test run_async(ex, [&](auto&& r) { completed = true; - winner_index = r.first; + winner_index = r.index(); }, [](std::exception_ptr) {})( when_any(io_op(), returns_int(99)));