NVIDIA / NVIDIA/stdexec

Sequence sender compilation failure with STDEXEC_ENABLE_EXTRA_TYPE_CHECKING

Closed
#2,111 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
2.4k
Forks
270
Avg merge
3d 6h
Merged PRs (30d)
39

Description

##Description

any_sequence_sender | exec::transform_each(...) fails to compile when STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON.

The issue is that the exec::sequence validation paths in sequence_senders.hpp call completion_signatures_of_t, which under extra type checking routes into __debug_sender — a path designed for regular senders, not sequence senders.

Steps to Reproduce

  1. Clone stdexec at commit 61fb73d7
  2. Enable STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON
  3. Compose any_sequence_sender | transform_each(...)

Minimal reproducer:

#include <exec/sequence/any_sequence_of.hpp>
#include <exec/sequence/transform_each.hpp>
#include <exec/sequence/iterate.hpp>
#include <stdexec/execution.hpp>

using item_sigs = stdexec::completion_signatures<
    stdexec::set_value_t(int),
    stdexec::set_error_t(std::exception_ptr),
    stdexec::set_stopped_t()>;

using erased_seq_t = exec::any_sequence_sender<
    exec::any_sequence_receiver<item_sigs>>;

int main() {
    // Build a concrete sequence sender, erase to any_sequence_sender,
    // then compose with transform_each — this is the failing pattern.
    auto concrete = exec::iterate(std::vector{1, 2});
    erased_seq_t erased = std::move(concrete);

    auto pipe = std::move(erased)
        | exec::transform_each(stdexec::then([](int v) { return v + 1; }));
    (void)pipe;
}

Root Cause

Two places in sequence_senders.hpp use the regular completion_signatures_of_t for sequence sender checks:

  • __sequence_receiver_from concept (line ~687)
  • subscribe_t::__type_check_arguments (line ~758)

When extra type checking is ON, these instantiate __debug_sender which tries connect() — but sequence senders are not regular senders and don't provide connect().

Additionally, transform_each_t::get_env returns the child environment by value, which fails when the child is any_sequence_sender (immovable environment proxy). The function also has a recursive static_assert(sender_for<...>) that re-enters the regular sender concept.

Proposed Fix

File 1: include/exec/sequence_senders.hpp

Use __sequence_completion_signatures_of_t instead of completion_signatures_of_t in the two sequence-specific checks:

 // __sequence_receiver_from (line ~687)
-      STDEXEC::completion_signatures_of_t<_Sequence, STDEXEC::env_of_t<_Receiver>>>;
+      __sequence_completion_signatures_of_t<_Sequence, STDEXEC::env_of_t<_Receiver>>>;

 // subscribe_t::__type_check_arguments (line ~758)
-          using __checked_signatures [[maybe_unused]] = completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>;
+          using __checked_signatures [[maybe_unused]] = __sequence_completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>;

File 2: include/exec/sequence/transform_each.hpp

Return child environment by reference and remove the recursive sender_for check:

-      static auto get_env(_Sexpr const & __sexpr) noexcept -> env_of_t<__child_of<_Sexpr>>
+      static decltype(auto) get_env(_Sexpr const & __sexpr) noexcept
       {
-        static_assert(sender_for<_Sexpr, transform_each_t>);
         return __apply([]<class _Child>(__ignore, __ignore, _Child const & __child)
-                       { return STDEXEC::get_env(__child); },
+                       -> decltype(auto) { return STDEXEC::get_env(__child); },
                        __sexpr);
       }

Full unified diff

diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp
--- a/include/exec/sequence/transform_each.hpp
+++ b/include/exec/sequence/transform_each.hpp
@@ -217,7 +217,6 @@ struct transform_each_t
       }

       template <class _Sexpr>
-      static auto get_env(_Sexpr const & __sexpr) noexcept -> env_of_t<__child_of<_Sexpr>>
+      static decltype(auto) get_env(_Sexpr const & __sexpr) noexcept
       {
-        static_assert(sender_for<_Sexpr, transform_each_t>);
         return __apply([]<class _Child>(__ignore, __ignore, _Child const & __child)
-                       { return STDEXEC::get_env(__child); },
+                       -> decltype(auto) { return STDEXEC::get_env(__child); },
                        __sexpr);
       }
     };
   }  // namespace __transform_each

diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp
--- a/include/exec/sequence_senders.hpp
+++ b/include/exec/sequence_senders.hpp
@@ -684,7 +684,7 @@ concept __sequence_receiver_from =
     sequence_sender_in<_Sequence, STDEXEC::env_of_t<_Receiver>>
     && STDEXEC::receiver_of<
       _Receiver,
-      STDEXEC::completion_signatures_of_t<_Sequence, STDEXEC::env_of_t<_Receiver>>>;
+      __sequence_completion_signatures_of_t<_Sequence, STDEXEC::env_of_t<_Receiver>>>;

   template <class _Receiver, class _Sequence>
   concept sequence_receiver_from =
@@ -753,11 +753,11 @@ struct subscribe_t
         if constexpr (sequence_sender_in<_Sequence, env_of_t<_Receiver>>)
         {
-          // Instantiate __debug_sender via completion_signatures_of_t and
+          // Instantiate sequence completion signatures and
           // item_types_of_t to check that the actual completions and item_types
           // match the expected completions and values.
           using __checked_signatures
-            [[maybe_unused]] = completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>;
+            [[maybe_unused]] = __sequence_completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>;
           using __checked_item_types
             [[maybe_unused]] = item_types_of_t<_Sequence, env_of_t<_Receiver>>;
         }
         else
         {
           __diagnose_sequence_sender_concept_failure<_Sequence, env_of_t<_Receiver>>();
         }
         return true;
       }

Environment

Item Value
stdexec 61fb73d7
Trigger STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON
Platform Windows x64, C++20
Build config STDEXEC_MAIN_PROJECT=OFF (subdirectory)

Verification

Confirmed on stdexec@61fb73d7 with STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON:

  • Without patch: frame_source.cc (which uses any_sequence_sender | transform_each(stdexec::then(...))) fails with error C3889: connect_t__debug_sender tries connect() on a transform_each sequence expression.
  • With patch: full project builds successfully (Debug, Release, ASan presets).

Additional Context

The exec::sequence extension is experimental. This is not a compiler-specific issue — the extra type checking paths conflate sequence and regular sender metadata. A regression test for any_sequence_sender | transform_each with extra type checking enabled would be useful.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reproducer in frame_source.cc and enable STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON to observe the failure. Inspect include/exec/sequence_senders.hpp and include/exec/sequence/transform_each.hpp, then verify that the any_sequence_sender composition and the full Debug, Release, and ASan presets build successfully.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.