1111#define BOOST_CAPY_WHEN_ALL_HPP
1212
1313#include < boost/capy/detail/config.hpp>
14+ #include < boost/capy/detail/void_to_monostate.hpp>
1415#include < boost/capy/concept/executor.hpp>
1516#include < boost/capy/concept/io_awaitable.hpp>
1617#include < coroutine>
@@ -32,19 +33,6 @@ namespace capy {
3233
3334namespace detail {
3435
35- /* * Type trait to filter void types from a tuple.
36-
37- Void-returning tasks do not contribute a value to the result tuple.
38- This trait computes the filtered result type.
39-
40- Example: filter_void_tuple_t<int, void, string> = tuple<int, string>
41- */
42- template <typename T>
43- using wrap_non_void_t = std::conditional_t <std::is_void_v<T>, std::tuple<>, std::tuple<T>>;
44-
45- template <typename ... Ts>
46- using filter_void_tuple_t = decltype (std::tuple_cat(std::declval<wrap_non_void_t <Ts>>()...));
47-
4836/* * Holds the result of a single task within when_all.
4937*/
5038template <typename T>
@@ -63,11 +51,12 @@ struct result_holder
6351 }
6452};
6553
66- /* * Specialization for void tasks - no value storage needed .
54+ /* * Specialization for void tasks - returns monostate to preserve index mapping .
6755*/
6856template <>
6957struct result_holder <void >
7058{
59+ std::monostate get () && { return {}; }
7160};
7261
7362/* * Shared state for when_all operation.
@@ -358,45 +347,38 @@ class when_all_launcher
358347 }
359348};
360349
361- /* * Helper to extract a single result, returning empty tuple for void .
350+ /* * Helper to extract a single result from state .
362351 This is a separate function to work around a GCC-11 ICE that occurs
363352 when using nested immediately-invoked lambdas with pack expansion.
364353*/
365354template <std::size_t I, typename ... Ts>
366355auto extract_single_result (when_all_state<Ts...>& state)
367356{
368- using T = std::tuple_element_t <I, std::tuple<Ts...>>;
369- if constexpr (std::is_void_v<T>)
370- return std::tuple<>();
371- else
372- return std::make_tuple (std::move (std::get<I>(state.results_ )).get ());
357+ return std::move (std::get<I>(state.results_ )).get ();
373358}
374359
375- /* * Extract results from state, filtering void types .
360+ /* * Extract all results from state as a tuple .
376361*/
377362template <typename ... Ts>
378363auto extract_results (when_all_state<Ts...>& state)
379364{
380365 return [&]<std::size_t ... Is>(std::index_sequence<Is...>) {
381- return std::tuple_cat (extract_single_result<Is>(state)...);
366+ return std::tuple (extract_single_result<Is>(state)...);
382367 }(std::index_sequence_for<Ts...>{});
383368}
384369
385370} // namespace detail
386371
387- /* * Compute a tuple type with void types filtered out .
372+ /* * Compute the when_all result tuple type .
388373
389- Returns void when all types are void (P2300 aligned),
390- otherwise returns a std::tuple with void types removed .
374+ Void-returning tasks contribute std::monostate to preserve the
375+ task-index-to-result-index mapping, matching when_any's approach .
391376
392- Example: non_void_tuple_t <int, void, string> = std::tuple<int, string>
393- Example: non_void_tuple_t <void, void> = void
377+ Example: when_all_result_t <int, void, string> = std::tuple<int, std::monostate , string>
378+ Example: when_all_result_t <void, void> = std::tuple<std::monostate, std::monostate>
394379*/
395380template <typename ... Ts>
396- using non_void_tuple_t = std::conditional_t <
397- std::is_same_v<detail::filter_void_tuple_t <Ts...>, std::tuple<>>,
398- void ,
399- detail::filter_void_tuple_t <Ts...>>;
381+ using when_all_result_t = std::tuple<void_to_monostate_t <Ts>...>;
400382
401383/* * Execute multiple awaitables concurrently and collect their results.
402384
@@ -407,8 +389,8 @@ using non_void_tuple_t = std::conditional_t<
407389
408390 @li All child awaitables run concurrently on the caller's executor
409391 @li Results are returned as a tuple in input order
410- @li Void-returning awaitables do not contribute to the result tuple
411- @li If all awaitables return void, `when_all` returns ` task<void>`
392+ @li Void-returning awaitables contribute std::monostate to the
393+ result tuple, preserving the task-index-to-result-index mapping
412394 @li First exception wins; subsequent exceptions are discarded
413395 @li Stop is requested for siblings on first error
414396 @li Completes only after all children have finished
@@ -422,8 +404,8 @@ using non_void_tuple_t = std::conditional_t<
422404 satisfy @ref IoAwaitable and is consumed (moved-from) when
423405 `when_all` is awaited.
424406
425- @return A task yielding a tuple of non-void results. Returns
426- `task<void>` when all input awaitables return void .
407+ @return A task yielding a tuple of results in input order. Void tasks
408+ contribute std::monostate to preserve index correspondence .
427409
428410 @par Example
429411
@@ -436,23 +418,22 @@ using non_void_tuple_t = std::conditional_t<
436418 fetch_posts( id ) // task<std::vector<Post>>
437419 );
438420
439- // Void awaitables don't contribute to result
440- co_await when_all(
441- log_event( "start" ), // task<void>
442- notify_user( id ) // task<void>
421+ // Void awaitables contribute monostate
422+ auto [a, _, b] = co_await when_all(
423+ fetch_int(), // task<int>
424+ log_event( "start" ), // task<void> → monostate
425+ fetch_str() // task<string>
443426 );
444- // Returns task<void>, no result tuple
427+ // a is int, _ is monostate, b is string
445428 }
446429 @endcode
447430
448431 @see IoAwaitable, task
449432*/
450433template <IoAwaitable... As>
451434[[nodiscard]] auto when_all (As... awaitables)
452- -> task<non_void_tuple_t <awaitable_result_t<As>...>>
435+ -> task<when_all_result_t <awaitable_result_t<As>...>>
453436{
454- using result_type = non_void_tuple_t <awaitable_result_t <As>...>;
455-
456437 // State is stored in the coroutine frame, using the frame allocator
457438 detail::when_all_state<awaitable_result_t <As>...> state;
458439
@@ -469,11 +450,7 @@ template<IoAwaitable... As>
469450 if (state.first_exception_ )
470451 std::rethrow_exception (state.first_exception_ );
471452
472- // Extract and return results
473- if constexpr (std::is_void_v<result_type>)
474- co_return ;
475- else
476- co_return detail::extract_results (state);
453+ co_return detail::extract_results (state);
477454}
478455
479456} // namespace capy
0 commit comments