Skip to content

Sequence sender compilation failure with STDEXEC_ENABLE_EXTRA_TYPE_CHECKING #2111

@0xMonad

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions