Skip to content

Sequence sender compilation failure with STDEXEC_ENABLE_EXTRA_TYPE_CHECKING #2111

Description

@0xMonad

##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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions