Repository: NVIDIA/stdexec (2026-04-07T00:30:50Z)

Issue:
when_all_with_variant with a sender with multiple value completions won't compile
I am running into compilation errors when `sync_wait` takes a `when_all_with_variant` that has a sender with multiple value completions.  The code is creating an object of type `tuple<T&&>` instead of `tuple<T>`, and that leads to errors trying to initialize a `variant` that expects a `tuple<T>`.  (Multiple value completions is the whole point of `when_all_with_variant`, so this bug makes that adapter kind of useless.)

```
#include <stdexec/execution.hpp>
#include <exec/any_sender_of.hpp>

int main() {
  using any_sender =
    exec::any_sender<exec::any_receiver<stdexec::completion_signatures<
      stdexec::set_value_t(int), stdexec::set_value_t(double)>>>;
  stdexec::sync_wait(
      stdexec::when_all_with_variant(
        any_sender{stdexec::just(1)}));
}
```

When compiled with `nvc++`, the error is right at the top and is easy enough to figure out.  Note the "argument types are: (std::tuple<int &&>)".
```
"/proj/cuda/stdexec/main/include/stdexec/__detail/__into_variant.hpp", line 86: error: no instance of constructor "std::variant<_Types...>::variant [with _Types=<std::tuple<double>, std::tuple<int>>]" matches the argument list
            argument types are: (std::tuple<int &&>)
                                 __variant_t{
                                            ^
```

When compiled with `g++`, the real cause of the error is much harder to find, but I think it's the same error.  Note the "with _Tp = std::tuple<int&&>&&;" in there.
```
/usr/include/c++/15/variant:1499:9: note: candidate 5: ‘template<class _Tp, class, class, class _Tj, class> constexpr std::variant< <template-parameter-1-1> >::variant(_Tp&&) [with <template-parameter-2-2> = _Tp; _Tj = <template-parameter-1-2>; _Types = {std::tuple<double>, std::tuple<int>}]’
 1499 |         variant(_Tp&& __t)
      |         ^~~~~~~
/usr/include/c++/15/variant:1499:9: note: template argument deduction/substitution failed:
/usr/include/c++/15/variant: In substitution of ‘template<class ... _Types> template<class _Tp, class> using std::variant< <template-parameter-1-1> >::__accepted_type = std::variant< <template-parameter-1-1> >::__to_type<((long unsigned int)__accepted_index<_Tp>)> [with _Tp = std::tuple<int&&>&&; <template-parameter-2-2> = void; _Types = {std::tuple<double>, std::tuple<int>}]’:
```



Discussion:
> `with _Tp = std::tuple<int&&>&&`

that strange tuple type is intended. the `tuple<Args&&...>` object is being used to initialize a `tuple<decay_t<Args>...>` object, which works because the types are convertible, and doing it this way saves a move.

the problem is that the `std::variant` constructor is not able to determine _which_ alternative is being initialized, probably because there is an ambiguity. the fix is to explicitly specify the alternative type to the constructor with `std::in_place_type<std::tuple<std::decay_t<Args>...>>`. then it works as intended.



Patch:
diff --git a/include/stdexec/__detail/__into_variant.hpp b/include/stdexec/__detail/__into_variant.hpp
index 0f011bf8b..adc52c703 100644
--- a/include/stdexec/__detail/__into_variant.hpp
+++ b/include/stdexec/__detail/__into_variant.hpp
@@ -83,8 +83,9 @@ namespace STDEXEC
           STDEXEC_TRY
           {
             STDEXEC::set_value(static_cast<_State&&>(__state).__rcvr_,
-                               __variant_t{
-                                 std::tuple<_Args&&...>{static_cast<_Args&&>(__args)...}});
+                               __variant_t{std::in_place_type<__decayed_std_tuple<_Args...>>,
+                                           std::tuple<_Args&&...>{
+                                             static_cast<_Args&&>(__args)...}});
           }
           STDEXEC_CATCH_ALL
           {
diff --git a/include/stdexec/__detail/__when_all.hpp b/include/stdexec/__detail/__when_all.hpp
index 97a8b13c6..a11e82eaf 100644
--- a/include/stdexec/__detail/__when_all.hpp
+++ b/include/stdexec/__detail/__when_all.hpp
@@ -56,6 +56,16 @@ namespace STDEXEC
 
   struct when_all_with_variant_t
   {
+   private:
+    static constexpr auto __mk_transform_fn() noexcept
+    {
+      return []<class... _Child>(__ignore, __ignore, _Child&&... __child)
+      {
+        return when_all(into_variant(static_cast<_Child&&>(__child))...);
+      };
+    }
+
+   public:
     template <sender... _Senders>
     constexpr auto operator()(_Senders&&... __sndrs) const -> __well_formed_sender auto
     {
@@ -66,9 +76,7 @@ namespace STDEXEC
     static constexpr auto transform_sender(set_value_t, _Sender&& __sndr, __ignore)
     {
       // transform when_all_with_variant(sndrs...) into when_all(into_variant(sndrs)...).
-      return __apply([&]<class... _Child>(__ignore, __ignore, _Child&&... __child)
-                     { return when_all(into_variant(static_cast<_Child&&>(__child))...); },
-                     static_cast<_Sender&&>(__sndr));
+      return __apply(__mk_transform_fn(), static_cast<_Sender&&>(__sndr));
     }
   };
 


Test patch:
diff --git a/test/stdexec/algos/adaptors/test_when_all.cpp b/test/stdexec/algos/adaptors/test_when_all.cpp
index 0316806e5..631eb304e 100644
--- a/test/stdexec/algos/adaptors/test_when_all.cpp
+++ b/test/stdexec/algos/adaptors/test_when_all.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <catch2/catch.hpp>
+#include <exec/any_sender_of.hpp>
 #include <exec/async_scope.hpp>
 #include <exec/env.hpp>
 #include <exec/sender_for.hpp>
@@ -461,4 +462,13 @@ namespace
     scope.request_stop();
     ex::sync_wait(scope.on_empty());
   }
+
+  TEST_CASE("regression test for #1988", "[adaptors][when_all")
+  {
+    // just test that this compiles:
+    using any_sender = exec::any_sender<
+      exec::any_receiver<ex::completion_signatures<ex::set_value_t(int), ex::set_value_t(double)>>>;
+    ex::sync_wait(ex::when_all_with_variant(any_sender{ex::just(1)}));
+    SUCCEED();
+  }
 }  // namespace