diff --git a/include/stdexec/__detail/__spawn.hpp b/include/stdexec/__detail/__spawn.hpp index a8ce250c3..9b7211f53 100644 --- a/include/stdexec/__detail/__spawn.hpp +++ b/include/stdexec/__detail/__spawn.hpp @@ -137,22 +137,21 @@ namespace STDEXEC //! @brief A sender consumer that eagerly starts a sender and ties its //! lifetime to an *async scope*. //! - //! @c spawn is the standard "fire-and-forget into a scope" consumer. - //! You give it a sender, a @c scope_token (a handle to an async scope), - //! and optionally an environment, and @c spawn: + //! @c spawn is the standard "fire-and-forget into a scope" consumer. You give + //! it a sender, a @c scope_token (a handle to an async scope), and optionally + //! an environment, and @c spawn: //! - //! 1. allocates an operation state on the heap (using an allocator - //! queried from the environment or the sender's own environment), + //! 1. allocates an operation state on the heap (using an allocator queried + //! from the environment or the sender's own environment), //! 2. tries to associate the resulting operation with the scope via //! `token.try_associate()`, - //! 3. if the association succeeds, eagerly @c start s the operation, - //! and on completion deallocates the state and releases the scope - //! association. + //! 3. if the association succeeds, eagerly @c start s the operation, and on + //! completion deallocates the state and releases the scope association. //! //! If association fails (typically because the scope has already begun - //! shutting down), @c spawn destroys the state and returns without - //! starting the operation. The result of the sender, if any, is - //! discarded — @c spawn returns @c void. + //! shutting down), @c spawn destroys the state and returns without starting + //! the operation. The result of the sender, if any, is discarded — @c spawn + //! returns @c void. //! //! See [exec.spawn] in the C++26 working draft for the normative //! specification. @@ -172,27 +171,27 @@ namespace STDEXEC //! //! The argument sender must not be able to complete with @c set_error //! — @c spawn cannot deliver an error to a non-existent caller. The - //! @c requires clause enforces this with a - //! `__never_sends` check; the diagnostic - //! overload says "spawn expects a sender that cannot fail" if the check - //! fires. + //! @c requires clause enforces this with a `__never_sends` + //! check. The diagnostic overload first checks that the sender after scope + //! wrapping and environment injection is valid in the selected environment + //! and delegates to the sender diagnostics if that check fails; otherwise, it + //! reports that @c spawn expects a sender that cannot fail. //! - //! Successful and stopped completions are both accepted; their results - //! are discarded. + //! Successful and stopped completions are both accepted; their results are + //! discarded. //! //! **Scope semantics.** //! - //! The scope is the *owner of lifetime* for the spawned operation. - //! Calling code is expected to eventually @c join() the scope (or - //! otherwise wait for all spawned work to drain) before destroying it - //! — typically once at program shutdown, or once per logical unit of - //! related background work. + //! The scope is the *owner of lifetime* for the spawned operation. Calling + //! code is expected to eventually @c join() the scope (or otherwise wait for + //! all spawned work to drain) before destroying it — typically once at + //! program shutdown, or once per logical unit of related background work. //! - //! @c spawn is the canonical fire-and-forget consumer for any work - //! that has a clear "owning context" (a request, a session, a worker). - //! For top-level work with no owning scope, use @c exec::start_detached - //! (an stdexec extension). For fire-and-forget work whose completion - //! you want to *observe* (without blocking), use + //! @c spawn is the canonical fire-and-forget consumer for any work that has a + //! clear "owning context" (a request, a session, a worker). For top-level + //! work with no owning scope, use @c exec::start_detached (an stdexec + //! extension). For fire-and-forget work whose completion you want to + //! *observe* (without blocking), use //! @c stdexec::spawn_future. //! //! @see stdexec::spawn_future — like @c spawn, but returns a sender that @@ -236,9 +235,11 @@ namespace STDEXEC // with the primary three-argument overload above (they differ only by a // constraint), which the Sphinx C++ domain cannot disambiguate. # if !defined(STDEXEC_DOXYGEN_INVOKED) - //! @brief Diagnostic overload — selected when the sender's completion - //! signatures include @c set_error_t. Emits a @c static_assert - //! explaining that @c spawn expects a sender that cannot fail. + //! @brief Diagnostic overload selected when the no-error requirement fails. + //! If the sender after scope wrapping and environment injection is + //! not valid in the selected environment, delegates to the sender + //! diagnostics; otherwise, emits a @c static_assert explaining that + //! @c spawn expects a sender that cannot fail. //! //! Not normally called; the @c requires clause on the primary overload //! steers compilation here on a constraint failure. @@ -246,9 +247,15 @@ namespace STDEXEC void operator()(_Sender&&, _Token, _Env&&) const { using _spawn_sndr_t = spawn_t::_spawn_sndr_t<_Sender, _Token, _Env>; - static_assert(sender_in<_spawn_sndr_t, _Env> - && __never_sends, - "spawn expects a sender that cannot fail"); + if constexpr (!sender_in<_spawn_sndr_t, _Env>) + { + __diagnose_sender_concept_failure<_spawn_sndr_t, _Env>(); + } + else + { + static_assert(__never_sends, + "spawn expects a sender that cannot fail"); + } } # endif // !defined(STDEXEC_DOXYGEN_INVOKED) diff --git a/include/stdexec/__detail/__spawn_future.hpp b/include/stdexec/__detail/__spawn_future.hpp index cc1f6dd0d..f466a7a98 100644 --- a/include/stdexec/__detail/__spawn_future.hpp +++ b/include/stdexec/__detail/__spawn_future.hpp @@ -897,6 +897,11 @@ namespace STDEXEC using __alloc_t = decltype(__spawn_common::__choose_alloc(__env, STDEXEC::get_env(__sndr))); using __senv_t = decltype(__spawn_common::__choose_senv(__env, STDEXEC::get_env(__sndr))); + if constexpr (!sender_in<_Sender, __senv_t>) + { + __diagnose_sender_concept_failure<_Sender, __senv_t>(); + } + using __spawn_future_state_t = __spawn_future::__spawn_future_state<__alloc_t, std::remove_cvref_t<_Token>,