diff --git a/include/stdexec/__detail/__stopped_as_error.hpp b/include/stdexec/__detail/__stopped_as_error.hpp index ba549820e..03c5f1060 100644 --- a/include/stdexec/__detail/__stopped_as_error.hpp +++ b/include/stdexec/__detail/__stopped_as_error.hpp @@ -26,9 +26,13 @@ import stdexec; # include "__execution_fwd.hpp" // include these after __execution_fwd.hpp +# include "__basic_sender.hpp" # include "__concepts.hpp" +# include "__diagnostics.hpp" +# include "__domain.hpp" # include "__just.hpp" # include "__let.hpp" +# include "__queries.hpp" # include "__sender_adaptor_closure.hpp" # include "__senders.hpp" @@ -38,6 +42,19 @@ namespace STDEXEC { ///////////////////////////////////////////////////////////////////////////// // [exec.stopped.err] + namespace __sae + { + template + struct __just_error_fn + { + constexpr auto operator()() noexcept(__nothrow_move_constructible<_Error>) + { + return just_error(static_cast<_Error&&>(__err_)); + } + + _Error __err_; + }; + } // namespace __sae //! @brief A pipeable sender adaptor that converts a predecessor's stopped //! completion into an error completion carrying a user-supplied @@ -60,12 +77,17 @@ namespace STDEXEC //! //! **Equivalence.** //! - //! stopped_as_error(sndr, err) is implemented (and is - //! observationally equivalent to) + //! stopped_as_error(sndr, err) is lowered (via + //! @c transform_sender) to, and is observationally equivalent to, //! let_stopped(sndr, [err]{ return just_error(err); }). //! Use this adaptor whenever you would have written that pattern by - //! hand — it is shorter, clearer at the call site, and the - //! implementation can be specialized more efficiently in the future. + //! hand — it is shorter and clearer at the call site. + //! + //! **Customization.** + //! + //! The sender returned by @c stopped_as_error has tag type + //! @c stopped_as_error_t, so a domain can customize it by providing a + //! @c transform_sender overload for senders of that tag. //! //! **Completion signatures.** //! @@ -122,10 +144,8 @@ namespace STDEXEC template constexpr auto operator()(_Sender&& __sndr, _Error __err) const -> __well_formed_sender auto { - return let_stopped(static_cast<_Sender&&>(__sndr), - [__err2 = static_cast<_Error&&>(__err)]() mutable noexcept( - __nothrow_move_constructible<_Error>) - { return just_error(static_cast<_Error&&>(__err2)); }); + return __make_sexpr(static_cast<_Error&&>(__err), + static_cast<_Sender&&>(__sndr)); } //! @brief Construct a sender-adaptor closure for the pipe form. @@ -138,6 +158,25 @@ namespace STDEXEC { return __closure(*this, static_cast<_Error&&>(__err)); } + + template <__decay_copyable _Sender> + static constexpr auto transform_sender(set_value_t, _Sender&& __sndr, __ignore) + { + static_assert(__sender_for<_Sender, stopped_as_error_t>); + auto& [__tag, __err, __child] = __sndr; + using __error_t = __decay_t; + return let_stopped(STDEXEC::__forward_like<_Sender>(__child), + __sae::__just_error_fn<__error_t>{ + STDEXEC::__forward_like<_Sender>(__err)}); + } + + template + static constexpr auto transform_sender(set_value_t, _Sender&&, __ignore) + { + return __not_a_sender<_WHAT_(_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_), + _WHERE_(_IN_ALGORITHM_, stopped_as_error_t), + _WITH_PRETTY_SENDER_<_Sender>>{}; + } }; //! @brief The customization point object for the @c stopped_as_error sender adaptor. @@ -147,6 +186,24 @@ namespace STDEXEC //! //! @hideinitializer inline constexpr stopped_as_error_t stopped_as_error{}; + + template <> + struct __sexpr_impl : __sexpr_defaults + { + static constexpr auto __get_attrs = + [](__ignore, __ignore, _Child const & __child) noexcept + { + return __sync_attrs{__child}; + }; + + template + static consteval auto __get_completion_signatures() + { + using __sndr_t = + __detail::__transform_sender_result_t>; + return STDEXEC::get_completion_signatures<__sndr_t, _Env...>(); + } + }; } // namespace STDEXEC # include "__epilogue.hpp" diff --git a/test/stdexec/algos/adaptors/test_stopped_as_error.cpp b/test/stdexec/algos/adaptors/test_stopped_as_error.cpp index c056d8125..40fc440dc 100644 --- a/test/stdexec/algos/adaptors/test_stopped_as_error.cpp +++ b/test/stdexec/algos/adaptors/test_stopped_as_error.cpp @@ -17,6 +17,7 @@ #include +#include #include #include #include @@ -161,4 +162,26 @@ namespace check_sends_stopped(ex::just(2) | ex::continues_on(sched2) | ex::stopped_as_error(-1)); check_sends_stopped(ex::just(3) | ex::continues_on(sched3) | ex::stopped_as_error(-1)); } + + // Return a different sender when we invoke this custom defined stopped_as_error implementation + struct stopped_as_error_test_domain + { + template Sender> + static auto transform_sender(ex::set_value_t, Sender&& sndr, auto const & env) + { + auto next_sndr = ex::stopped_as_error.transform_sender(ex::set_value, + static_cast(sndr), + env); + return std::move(next_sndr) + | ex::let_error([](auto) noexcept { return ex::just_error(-114514); }); + } + }; + + TEST_CASE("stopped_as_error can be customized", "[adaptors][stopped_as_error]") + { + basic_inline_scheduler sched; + auto snd = ex::just_stopped() | ex::continues_on(sched) | ex::stopped_as_error(-1); + auto op = ex::connect(std::move(snd), expect_error_receiver{-114514}); + ex::start(op); + } } // namespace