Skip to content

Commit 84f5f0f

Browse files
committed
exec::lifetime
Enables consumption of asynchronous objects. Accepts an invocable, and N asynchronous objects. Forms an operation which, when connected: 1. Provides the asynchronous objects with storage in its operation state 2. Passes all enter scope senders obtained in step 1 to exec:: enter_scopes to combine them 3. Connects the resulting enter scope sender And when started: 1. Starts the enter scope sender which was connected in step 3 above 2. If the operation started in step 1 sends error or stopped, completes immediately with that completion, otherwise passes a reference to all of the newly-constructed objects to the invocable, obtaining a sender 3. Connects and starts the sender obtained in step 2 4. Upon the completion of that operation, stores the completion thereof 5. Connects and starts the exit scope sender which destroys the objects constructed in step 1 6. Upon the completion of that operation, ends the overall operation with the completion stored in step 4
1 parent 5c7b6e8 commit 84f5f0f

3 files changed

Lines changed: 409 additions & 0 deletions

File tree

include/exec/lifetime.hpp

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* Copyright (c) 2025 Robert Leahy. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
*
6+
* Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://llvm.org/LICENSE.txt
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <cstddef>
22+
#include <functional>
23+
#include <memory>
24+
#include <tuple>
25+
#include <type_traits>
26+
#include <utility>
27+
28+
#include "enter_scope_sender.hpp"
29+
#include "enter_scopes.hpp"
30+
#include "invoke.hpp"
31+
#include "object.hpp"
32+
#include "within.hpp"
33+
#include "../stdexec/execution.hpp"
34+
35+
namespace experimental::execution {
36+
37+
namespace detail::lifetime {
38+
39+
template<typename F, typename... Objects>
40+
concept function = ::STDEXEC::sender<
41+
std::invoke_result_t<
42+
F,
43+
::exec::type_of_object_t<Objects>&...>>;
44+
45+
template<typename SenderFactory, typename... Objects>
46+
concept sender_factory = ::exec::enter_scope_sender<
47+
std::invoke_result_t<
48+
SenderFactory,
49+
::exec::enter_scope_sender_of_object_t<Objects>...>>;
50+
51+
struct t {
52+
template<typename F, ::exec::object... Objects>
53+
requires function<F, Objects...>
54+
constexpr ::STDEXEC::sender auto operator()(F&& f, Objects&&... objects) const
55+
noexcept(
56+
std::is_nothrow_constructible_v<
57+
std::remove_cvref_t<F>,
58+
F> &&
59+
(std::is_nothrow_constructible_v<
60+
std::remove_cvref_t<Objects>,
61+
Objects> && ...))
62+
{
63+
return (*this)(
64+
::exec::enter_scopes,
65+
(F&&)f,
66+
(Objects&&)objects...);
67+
}
68+
template<typename SenderFactory, typename F, ::exec::object... Objects>
69+
requires
70+
sender_factory<SenderFactory, Objects...> &&
71+
function<F, Objects...>
72+
constexpr ::STDEXEC::sender auto operator()(
73+
SenderFactory&& sender_factory,
74+
F&& f,
75+
Objects&&... objects) const noexcept(
76+
std::is_nothrow_constructible_v<
77+
std::remove_cvref_t<SenderFactory>,
78+
SenderFactory> &&
79+
std::is_nothrow_constructible_v<
80+
std::remove_cvref_t<F>,
81+
F> &&
82+
(std::is_nothrow_constructible_v<
83+
std::remove_cvref_t<Objects>,
84+
Objects> && ...))
85+
{
86+
return ::STDEXEC::__make_sexpr<t>(
87+
std::tuple(
88+
(SenderFactory&&)sender_factory,
89+
(F&&)f,
90+
(Objects&&)objects...));
91+
}
92+
};
93+
94+
template<typename T>
95+
class storage_for_object {
96+
alignas(T) std::byte buffer_[sizeof(T)];
97+
public:
98+
constexpr T* get_storage() noexcept {
99+
return reinterpret_cast<T*>(buffer_);
100+
}
101+
constexpr T& get_object() noexcept {
102+
return *std::launder(get_storage());
103+
}
104+
};
105+
106+
template<typename Object>
107+
using storage_for_object_t = storage_for_object<
108+
::exec::type_of_object_t<Object>>;
109+
110+
template<typename... Objects>
111+
using storage_for_objects_t = std::tuple<
112+
storage_for_object_t<Objects>...>;
113+
114+
template<typename Tuple, typename = std::remove_cvref_t<Tuple>>
115+
class make_sender;
116+
117+
template<
118+
typename Tuple,
119+
typename SenderFactory,
120+
typename F,
121+
typename... Objects>
122+
class make_sender<Tuple, std::tuple<SenderFactory, F, Objects...>> {
123+
static constexpr auto impl_(
124+
::STDEXEC::__copy_cvref_t<Tuple, F>&& f,
125+
storage_for_object_t<Objects>&... storage) noexcept(
126+
std::is_nothrow_constructible_v<
127+
F,
128+
::STDEXEC::__copy_cvref_t<Tuple, F>>)
129+
{
130+
return [f = (::STDEXEC::__copy_cvref_t<Tuple, F>&&)f, &storage...]() mutable noexcept(
131+
std::is_nothrow_invocable_v<
132+
F,
133+
::exec::type_of_object_t<Objects>&...>)
134+
{
135+
return std::invoke(
136+
std::move(f),
137+
storage.get_object()...);
138+
};
139+
}
140+
public:
141+
static constexpr bool nothrow = noexcept(
142+
::exec::within(
143+
std::invoke(
144+
std::declval<::STDEXEC::__copy_cvref_t<Tuple, SenderFactory>>(),
145+
std::invoke(
146+
std::declval<::STDEXEC::__copy_cvref_t<Tuple, Objects>>(),
147+
std::declval<::exec::type_of_object_t<Objects>*>())...),
148+
::STDEXEC::just() | ::exec::invoke(
149+
impl_(
150+
std::declval<::STDEXEC::__copy_cvref_t<Tuple, F>>(),
151+
std::declval<storage_for_object_t<Objects>&>()...))));
152+
using storage_type = storage_for_objects_t<Objects...>;
153+
static constexpr ::STDEXEC::sender auto impl(
154+
Tuple&& t,
155+
storage_type& storage) noexcept(nothrow)
156+
{
157+
return std::apply(
158+
[&](auto&& sender_factory, auto&& f, auto&&... objects) noexcept(nothrow)
159+
{
160+
return std::apply(
161+
[&](auto&... storage_for_object) noexcept(nothrow) {
162+
return ::exec::within(
163+
std::invoke(
164+
(decltype(sender_factory)&&)sender_factory,
165+
std::invoke(
166+
(decltype(objects)&&)objects,
167+
storage_for_object.get_storage())...),
168+
::STDEXEC::just() | ::exec::invoke(
169+
impl_((decltype(f)&&)f, storage_for_object...)));
170+
},
171+
storage);
172+
},
173+
(Tuple&&)t);
174+
}
175+
using sender_type = decltype(
176+
impl(
177+
std::declval<Tuple>(),
178+
std::declval<storage_for_objects_t<Objects...>&>()));
179+
};
180+
181+
template<typename Tuple, typename Receiver>
182+
class state {
183+
using impl_ = make_sender<Tuple>;
184+
typename impl_::storage_type storage_;
185+
::STDEXEC::connect_result_t<
186+
typename impl_::sender_type,
187+
Receiver> op_;
188+
public:
189+
explicit constexpr state(Tuple&& t, Receiver r) noexcept(impl_::nothrow)
190+
: op_(
191+
::STDEXEC::connect(
192+
impl_::impl((Tuple&&)t, storage_),
193+
(Receiver&&)r))
194+
{}
195+
constexpr void start() & noexcept {
196+
::STDEXEC::start(op_);
197+
}
198+
};
199+
200+
// TODO: We should be able to get a better "message" than this
201+
struct FAILED_TO_FORM_COMPLETION_SIGNATURES {};
202+
203+
class impl : public ::STDEXEC::__sexpr_defaults {
204+
template<typename Self, typename... Env>
205+
using completions_ = ::STDEXEC::completion_signatures_of_t<
206+
typename make_sender<::STDEXEC::__data_of<Self>>::sender_type,
207+
Env...>;
208+
public:
209+
template<typename Self, typename... Env>
210+
static consteval auto __get_completion_signatures() {
211+
if constexpr (::STDEXEC::__minvocable_q<completions_, Self, Env...>) {
212+
return completions_<Self, Env...>{};
213+
} else if constexpr (sizeof...(Env) == 0) {
214+
return STDEXEC::__dependent_sender<Self>();
215+
} else {
216+
return ::STDEXEC::__throw_compile_time_error<
217+
FAILED_TO_FORM_COMPLETION_SIGNATURES,
218+
::STDEXEC::_WITH_PRETTY_SENDER_<Self>>();
219+
}
220+
}
221+
static constexpr auto __get_state = []<typename Sender, typename Receiver>(
222+
Sender&& sender, Receiver r) noexcept(
223+
std::is_nothrow_constructible_v<
224+
state<::STDEXEC::__data_of<Sender>, Receiver>,
225+
::STDEXEC::__data_of<Sender>,
226+
Receiver>) -> state<::STDEXEC::__data_of<Sender>, Receiver>
227+
{
228+
auto&& [_, tuple] = (Sender&&)sender;
229+
return state<decltype(tuple), Receiver>(
230+
(decltype(tuple)&&)tuple,
231+
(Receiver&&)r);
232+
};
233+
static constexpr auto __start = [](auto& state) noexcept {
234+
state.start();
235+
};
236+
};
237+
238+
}
239+
240+
using lifetime_t = detail::lifetime::t;
241+
inline constexpr lifetime_t lifetime;
242+
243+
} // namespace exec
244+
245+
namespace STDEXEC {
246+
247+
template<>
248+
struct __sexpr_impl<::exec::lifetime_t> : ::exec::detail::lifetime::impl {};
249+
250+
}

test/exec/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ set(exec_test_sources
7272
test_enter_scope_sender.cpp
7373
test_enter_scopes.cpp
7474
test_within.cpp
75+
test_lifetime.cpp
7576
)
7677

7778
add_executable(test.exec ${exec_test_sources})

0 commit comments

Comments
 (0)