Skip to content

Commit c21105f

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 3bfabbd commit c21105f

File tree

3 files changed

+407
-0
lines changed

3 files changed

+407
-0
lines changed

include/exec/lifetime.hpp

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
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 "like_t.hpp"
32+
#include "object.hpp"
33+
#include "within.hpp"
34+
#include "../stdexec/execution.hpp"
35+
36+
namespace exec {
37+
38+
namespace detail::lifetime {
39+
40+
template<typename F, typename... Objects>
41+
concept function = ::STDEXEC::sender<
42+
std::invoke_result_t<
43+
F,
44+
::exec::type_of_object_t<Objects>&...>>;
45+
46+
template<typename SenderFactory, typename... Objects>
47+
concept sender_factory = ::exec::enter_scope_sender<
48+
std::invoke_result_t<
49+
SenderFactory,
50+
::exec::enter_scope_sender_of_object_t<Objects>...>>;
51+
52+
struct t {
53+
template<typename F, ::exec::object... Objects>
54+
requires function<F, Objects...>
55+
constexpr ::STDEXEC::sender auto operator()(F&& f, Objects&&... objects) const
56+
noexcept(
57+
std::is_nothrow_constructible_v<
58+
std::remove_cvref_t<F>,
59+
F> &&
60+
(std::is_nothrow_constructible_v<
61+
std::remove_cvref_t<Objects>,
62+
Objects> && ...))
63+
{
64+
return (*this)(
65+
::exec::enter_scopes,
66+
(F&&)f,
67+
(Objects&&)objects...);
68+
}
69+
template<typename SenderFactory, typename F, ::exec::object... Objects>
70+
requires
71+
sender_factory<SenderFactory, Objects...> &&
72+
function<F, Objects...>
73+
constexpr ::STDEXEC::sender auto operator()(
74+
SenderFactory&& sender_factory,
75+
F&& f,
76+
Objects&&... objects) const noexcept(
77+
std::is_nothrow_constructible_v<
78+
std::remove_cvref_t<SenderFactory>,
79+
SenderFactory> &&
80+
std::is_nothrow_constructible_v<
81+
std::remove_cvref_t<F>,
82+
F> &&
83+
(std::is_nothrow_constructible_v<
84+
std::remove_cvref_t<Objects>,
85+
Objects> && ...))
86+
{
87+
return ::STDEXEC::__make_sexpr<t>(
88+
std::tuple(
89+
(SenderFactory&&)sender_factory,
90+
(F&&)f,
91+
(Objects&&)objects...));
92+
}
93+
};
94+
95+
template<typename T>
96+
class storage_for_object {
97+
alignas(T) std::byte buffer_[sizeof(T)];
98+
public:
99+
constexpr T* get_storage() noexcept {
100+
return reinterpret_cast<T*>(buffer_);
101+
}
102+
constexpr T& get_object() noexcept {
103+
return *std::launder(get_storage());
104+
}
105+
};
106+
107+
template<typename Object>
108+
using storage_for_object_t = storage_for_object<
109+
::exec::type_of_object_t<Object>>;
110+
111+
template<typename... Objects>
112+
using storage_for_objects_t = std::tuple<
113+
storage_for_object_t<Objects>...>;
114+
115+
template<typename Tuple, typename = std::remove_cvref_t<Tuple>>
116+
class make_sender;
117+
118+
template<
119+
typename Tuple,
120+
typename SenderFactory,
121+
typename F,
122+
typename... Objects>
123+
class make_sender<Tuple, std::tuple<SenderFactory, F, Objects...>> {
124+
static constexpr auto impl_(
125+
::exec::like_t<Tuple, F>&& f,
126+
storage_for_object_t<Objects>&... storage) noexcept(
127+
std::is_nothrow_constructible_v<
128+
F,
129+
::exec::like_t<Tuple, F>>)
130+
{
131+
return [f = (::exec::like_t<Tuple, F>&&)f, &storage...]() mutable noexcept(
132+
std::is_nothrow_invocable_v<
133+
F,
134+
::exec::type_of_object_t<Objects>&...>)
135+
{
136+
return std::invoke(
137+
std::move(f),
138+
storage.get_object()...);
139+
};
140+
}
141+
public:
142+
static constexpr bool nothrow = noexcept(
143+
::exec::within(
144+
std::invoke(
145+
std::declval<::exec::like_t<Tuple, SenderFactory>>(),
146+
std::invoke(
147+
std::declval<::exec::like_t<Tuple, Objects>>(),
148+
std::declval<::exec::type_of_object_t<Objects>*>())...),
149+
::STDEXEC::just() | ::exec::invoke(
150+
impl_(
151+
std::declval<::exec::like_t<Tuple, F>>(),
152+
std::declval<storage_for_object_t<Objects>&>()...))));
153+
using storage_type = storage_for_objects_t<Objects...>;
154+
static constexpr ::STDEXEC::sender auto impl(
155+
Tuple&& t,
156+
storage_type& storage) noexcept(nothrow)
157+
{
158+
return std::apply(
159+
[&](auto&& sender_factory, auto&& f, auto&&... objects) noexcept(nothrow)
160+
{
161+
return std::apply(
162+
[&](auto&... storage_for_object) noexcept(nothrow) {
163+
return ::exec::within(
164+
std::invoke(
165+
(decltype(sender_factory)&&)sender_factory,
166+
std::invoke(
167+
(decltype(objects)&&)objects,
168+
storage_for_object.get_storage())...),
169+
::STDEXEC::just() | ::exec::invoke(
170+
impl_((decltype(f)&&)f, storage_for_object...)));
171+
},
172+
storage);
173+
},
174+
(Tuple&&)t);
175+
}
176+
using sender_type = decltype(
177+
impl(
178+
std::declval<Tuple>(),
179+
std::declval<storage_for_objects_t<Objects...>&>()));
180+
};
181+
182+
template<typename Tuple, typename Receiver>
183+
class state {
184+
using impl_ = make_sender<Tuple>;
185+
typename impl_::storage_type storage_;
186+
::STDEXEC::connect_result_t<
187+
typename impl_::sender_type,
188+
Receiver> op_;
189+
public:
190+
explicit constexpr state(Tuple&& t, Receiver r) noexcept(impl_::nothrow)
191+
: op_(
192+
::STDEXEC::connect(
193+
impl_::impl((Tuple&&)t, storage_),
194+
(Receiver&&)r))
195+
{}
196+
constexpr void start() & noexcept {
197+
::STDEXEC::start(op_);
198+
}
199+
};
200+
201+
// TODO: We should be able to get a better "message" than this
202+
struct FAILED_TO_FORM_COMPLETION_SIGNATURES {};
203+
204+
class impl : public ::STDEXEC::__sexpr_defaults {
205+
template<typename Self, typename... Env>
206+
using completions_ = ::STDEXEC::completion_signatures_of_t<
207+
typename make_sender<::STDEXEC::__data_of<Self>>::sender_type,
208+
Env...>;
209+
public:
210+
template<typename Self, typename... Env>
211+
static consteval auto get_completion_signatures() {
212+
if constexpr (::STDEXEC::__mvalid<completions_, Self, Env...>) {
213+
return completions_<Self, Env...>{};
214+
} else {
215+
return ::STDEXEC::__throw_compile_time_error<
216+
FAILED_TO_FORM_COMPLETION_SIGNATURES,
217+
::STDEXEC::_WITH_PRETTY_SENDER_<Self>>();
218+
}
219+
}
220+
static constexpr auto get_state = []<typename Sender, typename Receiver>(
221+
Sender&& sender, Receiver r) noexcept(
222+
std::is_nothrow_constructible_v<
223+
state<::STDEXEC::__data_of<Sender>, Receiver>,
224+
::STDEXEC::__data_of<Sender>,
225+
Receiver>) -> state<::STDEXEC::__data_of<Sender>, Receiver>
226+
{
227+
auto&& [_, tuple] = (Sender&&)sender;
228+
return state<decltype(tuple), Receiver>(
229+
(decltype(tuple)&&)tuple,
230+
(Receiver&&)r);
231+
};
232+
static constexpr auto start = [](auto& state) noexcept {
233+
state.start();
234+
};
235+
};
236+
237+
}
238+
239+
using lifetime_t = detail::lifetime::t;
240+
inline constexpr lifetime_t lifetime;
241+
242+
} // namespace exec
243+
244+
namespace STDEXEC {
245+
246+
template<>
247+
struct __sexpr_impl<::exec::lifetime_t> : ::exec::detail::lifetime::impl {};
248+
249+
}

test/exec/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ set(exec_test_sources
6868
test_enter_scope_sender.cpp
6969
test_enter_scopes.cpp
7070
test_within.cpp
71+
test_lifetime.cpp
7172
)
7273

7374
add_executable(test.exec ${exec_test_sources})

0 commit comments

Comments
 (0)