1717
1818#include " __execution_fwd.hpp"
1919
20+ #include < memory>
21+
2022#include " __meta.hpp"
2123#include " __env.hpp"
2224#include " __receivers.hpp"
23- #include " __submit.hpp"
25+ #include " __env.hpp"
26+ #include " __scope.hpp"
2427#include " __transform_sender.hpp"
25- #include " __type_traits.hpp"
2628
2729namespace stdexec {
2830 // ///////////////////////////////////////////////////////////////////////////
2931 // [execution.senders.consumer.start_detached]
3032 namespace __start_detached {
31- template <class _EnvId >
32- struct __detached_receiver {
33- using _Env = stdexec::__t <_EnvId>;
33+ template <class _SenderId , class _EnvId >
34+ struct __operation {
35+ using _Sender = __cvref_t <_SenderId>;
36+ using _Env = __t <_EnvId>;
37+
38+ explicit __operation (_Sender&& __sndr, _Env __env)
39+ : __env_(static_cast <_Env&&>(__env))
40+ , __op_state_(connect(static_cast <_Sender&&>(__sndr), __receiver{this })) {
41+ }
3442
35- struct __t {
43+ static void __destroy_delete (__operation* __self) noexcept {
44+ if constexpr (__callable<get_allocator_t , _Env>) {
45+ auto __alloc = stdexec::get_allocator (__self->__env_ );
46+ using _Alloc = decltype (__alloc);
47+ using _OpAlloc =
48+ typename std::allocator_traits<_Alloc>::template rebind_alloc<__operation>;
49+ _OpAlloc __op_alloc{__alloc};
50+ std::allocator_traits<_OpAlloc>::destroy (__op_alloc, __self);
51+ std::allocator_traits<_OpAlloc>::deallocate (__op_alloc, __self, 1 );
52+ } else {
53+ delete __self;
54+ }
55+ }
56+
57+ // The start_detached receiver deletes the operation state.
58+ struct __receiver {
3659 using receiver_concept = receiver_t ;
37- using __id = __detached_receiver ;
38- STDEXEC_ATTRIBUTE ((no_unique_address)) _Env __env_ ;
60+ using __t = __receiver ;
61+ using __id = __receiver ;
3962
4063 template <class ... _As>
4164 void set_value (_As&&...) noexcept {
65+ __operation::__destroy_delete (__op_); // NB: invalidates *this
4266 }
4367
4468 template <class _Error >
4569 [[noreturn]]
4670 void set_error (_Error&&) noexcept {
71+ // A detached operation failed. There is noplace for the error to go.
72+ // This is unrecoverable, so we terminate.
4773 std::terminate ();
4874 }
4975
5076 void set_stopped () noexcept {
77+ __operation::__destroy_delete (__op_); // NB: invalidates *this
5178 }
5279
5380 auto get_env () const noexcept -> const _Env& {
54- // BUGBUG NOT TO SPEC
55- return __env_;
81+ return __op_->__env_ ;
5682 }
83+
84+ __operation* __op_;
5785 };
58- };
5986
60- template <class _Env = env<>>
61- using __detached_receiver_t = __t <__detached_receiver<__id<__decay_t <_Env>>>>;
87+ STDEXEC_ATTRIBUTE ((no_unique_address)) _Env __env_;
88+ connect_result_t <_Sender, __receiver> __op_state_;
89+ };
6290
6391 struct start_detached_t {
6492 template <sender_in<__root_env> _Sender>
@@ -84,11 +112,36 @@ namespace stdexec {
84112 __as_root_env (static_cast <_Env&&>(__env)));
85113 }
86114
115+ // Below is the default implementation for `start_detached`.
87116 template <class _Sender , class _Env = __root_env>
88- requires sender_to<_Sender, __detached_receiver_t <_Env>>
89- void apply_sender (_Sender&& __sndr, _Env&& __env = {}) const {
90- __submit (
91- static_cast <_Sender&&>(__sndr), __detached_receiver_t <_Env>{static_cast <_Env&&>(__env)});
117+ requires sender_in<_Sender, __as_root_env_t <_Env>>
118+ void apply_sender (_Sender&& __sndr, _Env&& __env = {}) const noexcept (false ) {
119+ using _Op = __operation<__cvref_id<_Sender>, __id<__decay_t <_Env>>>;
120+ // Use the provided allocator, if any, to allocate the operation state.
121+ if constexpr (__callable<get_allocator_t , _Env>) {
122+ auto __alloc = get_allocator (__env);
123+ using _Alloc = decltype (__alloc);
124+ using _OpAlloc = typename std::allocator_traits<_Alloc>::template rebind_alloc<_Op>;
125+ // We use the allocator to allocate the operation state and also to construct
126+ // it.
127+ _OpAlloc __op_alloc{__alloc};
128+ _Op* __op = std::allocator_traits<_OpAlloc>::allocate (__op_alloc, 1 );
129+ __scope_guard __g{[__op, &__op_alloc]() noexcept {
130+ std::allocator_traits<_OpAlloc>::deallocate (__op_alloc, __op, 1 );
131+ }};
132+ // This can potentially throw. If it does, the scope guard will deallocate the
133+ // storage automatically.
134+ std::allocator_traits<_OpAlloc>::construct (
135+ __op_alloc, __op, static_cast <_Sender&&>(__sndr), static_cast <_Env&&>(__env));
136+ // The operation state is now constructed, dismiss the scope guard.
137+ __g.__dismiss ();
138+ // This cannot throw:
139+ stdexec::start (__op->__op_state_ );
140+ } else {
141+ // The caller did not provide an allocator, so we use the default allocator.
142+ _Op* __op = new _Op (static_cast <_Sender&&>(__sndr), static_cast <_Env&&>(__env));
143+ start (__op->__op_state_ );
144+ }
92145 }
93146 };
94147 } // namespace __start_detached
0 commit comments