##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
- Clone stdexec at commit
61fb73d7
- Enable
STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON
- 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.
##Description
any_sequence_sender | exec::transform_each(...)fails to compile whenSTDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON.The issue is that the
exec::sequencevalidation paths insequence_senders.hppcallcompletion_signatures_of_t, which under extra type checking routes into__debug_sender— a path designed for regular senders, not sequence senders.Steps to Reproduce
61fb73d7STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ONany_sequence_sender | transform_each(...)Minimal reproducer:
Root Cause
Two places in
sequence_senders.hppuse the regularcompletion_signatures_of_tfor sequence sender checks:__sequence_receiver_fromconcept (line ~687)subscribe_t::__type_check_arguments(line ~758)When extra type checking is ON, these instantiate
__debug_senderwhich triesconnect()— but sequence senders are not regular senders and don't provideconnect().Additionally,
transform_each_t::get_envreturns the child environment by value, which fails when the child isany_sequence_sender(immovable environment proxy). The function also has a recursivestatic_assert(sender_for<...>)that re-enters the regular sender concept.Proposed Fix
File 1:
include/exec/sequence_senders.hppUse
__sequence_completion_signatures_of_tinstead ofcompletion_signatures_of_tin the two sequence-specific checks:File 2:
include/exec/sequence/transform_each.hppReturn child environment by reference and remove the recursive sender_for check:
Full unified diff
Environment
61fb73d7STDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ONSTDEXEC_MAIN_PROJECT=OFF(subdirectory)Verification
Confirmed on
stdexec@61fb73d7withSTDEXEC_ENABLE_EXTRA_TYPE_CHECKING=ON:frame_source.cc(which usesany_sequence_sender | transform_each(stdexec::then(...))) fails witherror C3889: connect_t—__debug_sendertriesconnect()on atransform_eachsequence expression.Debug,Release,ASanpresets).Additional Context
The
exec::sequenceextension is experimental. This is not a compiler-specific issue — the extra type checking paths conflate sequence and regular sender metadata. A regression test forany_sequence_sender | transform_eachwith extra type checking enabled would be useful.