2020#include " __meta.hpp"
2121#include " __env.hpp"
2222#include " __receivers.hpp"
23- #include " __submit.hpp"
23+ #include " __env.hpp"
24+ #include " __scope.hpp"
2425#include " __transform_sender.hpp"
25- #include " __type_traits.hpp"
2626
2727namespace stdexec {
2828 // ///////////////////////////////////////////////////////////////////////////
2929 // [execution.senders.consumer.start_detached]
3030 namespace __start_detached {
31- template <class _EnvId >
32- struct __detached_receiver {
33- using _Env = stdexec::__t <_EnvId>;
31+ template <class _SenderId , class _EnvId >
32+ struct __operation {
33+ using _Sender = __cvref_t <_SenderId>;
34+ using _Env = __t <_EnvId>;
35+
36+ explicit __operation (_Sender&& __sndr, _Env __env)
37+ : __env_(static_cast <_Env&&>(__env))
38+ , __op_state_(connect(static_cast <_Sender&&>(__sndr), __receiver{this })) {
39+ }
3440
35- struct __t {
41+ // If the operation state was allocated with a user-provided allocator, then we must
42+ // use the allocator stored within the operation state to destroy the operation
43+ // state. This is a good time to use C++20's destroying delete operation.
44+ static void operator delete (__operation* __self, std::destroying_delete_t ) noexcept {
45+ if constexpr (__callable<get_allocator_t , _Env>) {
46+ auto __alloc = stdexec::get_allocator (__self->__env_ );
47+ using _Alloc = decltype (__alloc);
48+ using _OpAlloc =
49+ typename std::allocator_traits<_Alloc>::template rebind_alloc<__operation>;
50+ _OpAlloc __op_alloc{__alloc};
51+ std::allocator_traits<_OpAlloc>::destroy (__op_alloc, __self);
52+ std::allocator_traits<_OpAlloc>::deallocate (__op_alloc, __self, 1 );
53+ } else {
54+ std::destroy_at (__self);
55+ ::operator delete (__self);
56+ }
57+ }
58+
59+ // The start_detached receiver deletes the operation state.
60+ struct __receiver {
3661 using receiver_concept = receiver_t ;
37- using __id = __detached_receiver;
38- STDEXEC_ATTRIBUTE ((no_unique_address)) _Env __env_;
62+ using __t = __receiver;
63+ using __id = __receiver;
64+ __operation* __op_;
3965
4066 template <class ... _As>
4167 void set_value (_As&&...) noexcept {
68+ delete __op_; // NB: invalidates *this
4269 }
4370
4471 template <class _Error >
4572 [[noreturn]]
4673 void set_error (_Error&&) noexcept {
74+ // A detached operation failed. There is noplace for the error to go.
75+ // This is unrecoverable, so we terminate.
4776 std::terminate ();
4877 }
4978
5079 void set_stopped () noexcept {
80+ delete __op_; // NB: invalidates *this
5181 }
5282
5383 auto get_env () const noexcept -> const _Env& {
54- // BUGBUG NOT TO SPEC
55- return __env_;
84+ return __op_->__env_ ;
5685 }
5786 };
58- };
5987
60- template <class _Env = env<>>
61- using __detached_receiver_t = __t <__detached_receiver<__id<__decay_t <_Env>>>>;
88+ STDEXEC_ATTRIBUTE ((no_unique_address)) _Env __env_;
89+ connect_result_t <_Sender, __receiver> __op_state_;
90+ };
6291
6392 struct start_detached_t {
6493 template <sender_in<__root_env> _Sender>
@@ -84,11 +113,37 @@ namespace stdexec {
84113 __as_root_env (static_cast <_Env&&>(__env)));
85114 }
86115
116+ // Below is the default implementation for `start_detached`.
87117 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)});
118+ requires sender_in<_Sender, __as_root_env_t <_Env>>
119+ void apply_sender (_Sender&& __sndr, _Env&& __env = {}) const noexcept (false ) {
120+ using _Op = __operation<__cvref_id<_Sender>, __id<__decay_t <_Env>>>;
121+ // Use the provided allocator, if any, to allocate the operation state.
122+ if constexpr (__callable<get_allocator_t , _Env>) {
123+ auto __alloc = get_allocator (__env);
124+ using _Alloc = decltype (__alloc);
125+ using _OpAlloc = typename std::allocator_traits<_Alloc>::template rebind_alloc<_Op>;
126+ // We use the allocator to allocate the operation state and also to construct
127+ // it.
128+ _OpAlloc __op_alloc{__alloc};
129+ _Op* __op = std::allocator_traits<_OpAlloc>::allocate (__op_alloc, 1 );
130+ __scope_guard __g{[__op, &__op_alloc]() noexcept {
131+ std::allocator_traits<_OpAlloc>::deallocate (__op_alloc, __op, 1 );
132+ }};
133+ // This can potentially throw. If it does, the scope guard will deallocate the
134+ // storage automatically.
135+ std::allocator_traits<_OpAlloc>::construct (
136+ __op_alloc, __op, static_cast <_Sender&&>(__sndr), static_cast <_Env&&>(__env));
137+ // The operation state is now constructed, dismiss the scope guard.
138+ __g.__dismiss ();
139+ // This cannot throw:
140+ stdexec::start (__op->__op_state_ );
141+ } else {
142+ // The caller did not provide an allocator, so we use the default allocator.
143+ auto * __op = new __operation<__id<_Sender>, __id<__decay_t <_Env>>>{
144+ static_cast <_Sender&&>(__sndr), static_cast <_Env&&>(__env)};
145+ start (__op->__op_state_ );
146+ }
92147 }
93148 };
94149 } // namespace __start_detached
0 commit comments