Skip to content

Commit 66295d5

Browse files
authored
rewrite sender-adaptor-closure infra (#220)
* execution: complete sender_adaptor_closure conformance and adaptor migration - Refactor sender-adaptor closure infrastructure around sender_adaptor_closure, adaptor_closure, and composed closure piping. - Migrate adaptor implementations (then, bulk, let, continues_on, associate, on, affine_on) to the closure-based path. - Enforce [exec.adapt.obj] constraints and behavior: pipe equivalence, composition semantics, construction well-formedness, and noexcept propagation. - Expand sender_adaptor_closure tests for uniqueness detection, composition associativity, composed call pattern, partial application well-formedness, and pipe parity. - Deprecate direct detail::sender_adaptor alias in favor of make_sender_adaptor(...) factories and document the direct-use layout break (Adaptor moved into adaptor_closure_binding with [[no_unique_address]] for EBO, pending product_type support). - Apply quality cleanup in sender_adaptor_closure module/header exports and comments. - Add docs in headers. * remove duplicate import and legacy sender_adaptor.hpp includes on affine_on.hpp * rename is_sender_adaptor_closure_base to get_sender_adaptor_closure_base * isolate operator| in detail::pipeable for ADL hygiene Move the pipe operator overloads from beman::execution into detail::pipeable so they are found only via ADL through the closure_t base class. This prevents the operators from participating in overload resolution for unrelated types in beman::execution. - Reintroduce beman::execution::detail::pipeable namespace. - Add closure_t tag type as ADL anchor for sender_adaptor_closure - Remove export using of operator| from execution.cppm - Document detail::pipeable namespace in common.hpp * explicit use sender_adapter_closure on execution-syn.test * reexport operator| in execution.cppm to fix compilation issues with gcc/llvm presets
1 parent 07d53da commit 66295d5

19 files changed

Lines changed: 557 additions & 82 deletions

examples/intro-5-consumer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ struct expected_to_channel_t {
128128
sender<std::remove_cvref_t<CSender>> operator()(CSender&& child_sender) const {
129129
return {std::forward<CSender>(child_sender)};
130130
}
131-
auto operator()() const { return ex::detail::sender_adaptor{*this}; }
131+
auto operator()() const { return ex::detail::make_sender_adaptor(*this); }
132132
};
133133
inline constexpr expected_to_channel_t expected_to_channel{};
134134

include/beman/execution/detail/affine_on.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import beman.execution.detail.schedule;
3131
import beman.execution.detail.schedule_from;
3232
import beman.execution.detail.scheduler;
3333
import beman.execution.detail.sender;
34-
import beman.execution.detail.sender_adaptor;
3534
import beman.execution.detail.sender_adaptor_closure;
3635
import beman.execution.detail.sender_for;
3736
import beman.execution.detail.sender_has_affine_on;
@@ -53,7 +52,6 @@ import beman.execution.detail.write_env;
5352
#include <beman/execution/detail/schedule_from.hpp>
5453
#include <beman/execution/detail/scheduler.hpp>
5554
#include <beman/execution/detail/sender.hpp>
56-
#include <beman/execution/detail/sender_adaptor.hpp>
5755
#include <beman/execution/detail/sender_adaptor_closure.hpp>
5856
#include <beman/execution/detail/sender_for.hpp>
5957
#include <beman/execution/detail/sender_has_affine_on.hpp>
@@ -108,7 +106,7 @@ struct affine_on_t : ::beman::execution::sender_adaptor_closure<affine_on_t> {
108106
*
109107
* @return A sender adaptor for the affine_on_t.
110108
*/
111-
auto operator()() const { return ::beman::execution::detail::sender_adaptor{*this}; }
109+
auto operator()() const { return ::beman::execution::detail::make_sender_adaptor(*this); }
112110

113111
/**
114112
* @brief affine_on is implemented by transforming it into a use of schedule_from.

include/beman/execution/detail/associate.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import beman.execution.detail.make_sender;
2828
import beman.execution.detail.nothrow_callable;
2929
import beman.execution.detail.scope_token;
3030
import beman.execution.detail.sender;
31-
import beman.execution.detail.sender_adaptor;
31+
import beman.execution.detail.sender_adaptor_closure;
3232
import beman.execution.detail.set_stopped;
3333
import beman.execution.detail.set_value;
3434
import beman.execution.detail.start;
@@ -118,7 +118,7 @@ struct associate_t {
118118

119119
template <::beman::execution::scope_token Token>
120120
auto operator()(Token token) const {
121-
return ::beman::execution::detail::sender_adaptor{*this, ::std::move(token)};
121+
return ::beman::execution::detail::make_sender_adaptor(*this, ::std::move(token));
122122
}
123123

124124
public:

include/beman/execution/detail/bulk.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import beman.execution.detail.meta.unique;
2828
import beman.execution.detail.movable_value;
2929
import beman.execution.detail.product_type;
3030
import beman.execution.detail.sender;
31-
import beman.execution.detail.sender_adaptor;
3231
import beman.execution.detail.sender_adaptor_closure;
3332
import beman.execution.detail.set_error;
3433
import beman.execution.detail.set_value;
@@ -84,7 +83,8 @@ struct bulk_t : ::beman::execution::sender_adaptor_closure<bulk_t> {
8483
template <class Shape, class f>
8584
requires(std::is_integral_v<Shape> && ::beman::execution::detail::movable_value<f>)
8685
auto operator()(Shape&& shape, f&& fun) const {
87-
return beman::execution::detail::sender_adaptor{*this, std::forward<Shape>(shape), std::forward<f>(fun)};
86+
return ::beman::execution::detail::make_sender_adaptor(
87+
*this, std::forward<Shape>(shape), std::forward<f>(fun));
8888
}
8989

9090
template <class Sender, class Shape, class f>

include/beman/execution/detail/common.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,20 @@ namespace execution {
5959
* \brief Namespace for implementation details related to beman::execution
6060
* \internal
6161
*/
62-
namespace detail {}
62+
namespace detail {
63+
64+
/*!
65+
* \namespace beman::execution::detail::pipeable
66+
* \brief Namespace for ADL isolation of sender adaptor closure pipe operators.
67+
*
68+
* \details
69+
* The operator| overloads for sender adaptor closures are placed in this
70+
* namespace so they are only found via argument-dependent lookup when one
71+
* of the arguments derives from sender_adaptor_closure.
72+
* \internal
73+
*/
74+
namespace pipeable {}
75+
} // namespace detail
6376
} // namespace execution
6477
} // namespace beman
6578

include/beman/execution/detail/continues_on.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import beman.execution.detail.sched_attrs;
2727
import beman.execution.detail.schedule_from;
2828
import beman.execution.detail.scheduler;
2929
import beman.execution.detail.sender;
30-
import beman.execution.detail.sender_adaptor;
30+
import beman.execution.detail.sender_adaptor_closure;
3131
import beman.execution.detail.sender_for;
3232
import beman.execution.detail.transform_sender;
3333
#else
@@ -70,7 +70,7 @@ struct continues_on_t {
7070
}
7171
template <::beman::execution::scheduler Scheduler>
7272
auto operator()(Scheduler&& scheduler) const {
73-
return ::beman::execution::detail::sender_adaptor{*this, ::std::forward<Scheduler>(scheduler)};
73+
return ::beman::execution::detail::make_sender_adaptor(*this, ::std::forward<Scheduler>(scheduler));
7474
}
7575
template <::beman::execution::sender Sender, ::beman::execution::scheduler Scheduler>
7676
auto operator()(Sender&& sender, Scheduler&& scheduler) const {

include/beman/execution/detail/let.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import beman.execution.detail.movable_value;
4949
import beman.execution.detail.receiver;
5050
import beman.execution.detail.sched_env;
5151
import beman.execution.detail.sender;
52-
import beman.execution.detail.sender_adaptor;
52+
import beman.execution.detail.sender_adaptor_closure;
5353
import beman.execution.detail.set_error;
5454
import beman.execution.detail.set_stopped;
5555
import beman.execution.detail.set_value;
@@ -99,7 +99,7 @@ template <typename Completion>
9999
struct let_t {
100100
template <::beman::execution::detail::movable_value Fun>
101101
auto operator()(Fun&& fun) const {
102-
return ::beman::execution::detail::sender_adaptor{*this, ::std::forward<Fun>(fun)};
102+
return ::beman::execution::detail::make_sender_adaptor(*this, ::std::forward<Fun>(fun));
103103
}
104104
template <::beman::execution::sender Sender, ::beman::execution::detail::movable_value Fun>
105105
auto operator()(Sender&& sender, Fun&& fun) const {

include/beman/execution/detail/on.hpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import beman.execution.detail.query_with_default;
2727
import beman.execution.detail.sched_env;
2828
import beman.execution.detail.scheduler;
2929
import beman.execution.detail.sender;
30-
import beman.execution.detail.sender_adaptor;
3130
import beman.execution.detail.sender_adaptor_closure;
3231
import beman.execution.detail.sender_for;
3332
import beman.execution.detail.set_value;
@@ -157,8 +156,8 @@ struct on_t : ::beman::execution::sender_adaptor_closure<on_t> {
157156
}
158157
template <::beman::execution::scheduler Sch, ::beman::execution::detail::is_sender_adaptor_closure Closure>
159158
auto operator()(Sch&& sch, Closure&& closure) const {
160-
return ::beman::execution::detail::sender_adaptor{
161-
*this, ::std::forward<Sch>(sch), ::std::forward<Closure>(closure)};
159+
return ::beman::execution::detail::make_sender_adaptor(
160+
*this, ::std::forward<Sch>(sch), ::std::forward<Closure>(closure));
162161
}
163162
};
164163

include/beman/execution/detail/sender_adaptor.hpp

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,14 @@ import beman.execution.detail.sender_decompose;
2828
// ----------------------------------------------------------------------------
2929

3030
namespace beman::execution::detail {
31-
template <typename Adaptor, typename... T> //-dk:TODO detail export
32-
struct sender_adaptor : ::beman::execution::detail::product_type<::std::decay_t<Adaptor>, ::std::decay_t<T>...>,
33-
::beman::execution::sender_adaptor_closure<sender_adaptor<Adaptor, T...>> {
34-
template <::beman::execution::sender Sender, typename Self>
35-
static auto apply(Sender&& sender, Self&& self) {
36-
return [&self, &sender]<::std::size_t... I>(::std::index_sequence<I...>) {
37-
auto&& fun(self.template get<0>());
38-
return fun(::std::forward<Sender>(sender),
39-
::beman::execution::detail::forward_like<Self>(self.template get<I + 1>())...);
40-
}(::std::make_index_sequence<sender_adaptor::size() - 1u>{});
41-
}
42-
template <::beman::execution::sender Sender>
43-
auto operator()(Sender&& sender) {
44-
return apply(::std::forward<Sender>(sender), ::std::move(*this));
45-
}
46-
template <::beman::execution::sender Sender>
47-
auto operator()(Sender&& sender) const {
48-
return apply(::std::forward<Sender>(sender), *this);
49-
}
50-
};
31+
5132
template <typename... T>
52-
sender_adaptor(T&&...) -> sender_adaptor<T...>;
33+
using sender_adaptor
34+
[[deprecated("sender_adaptor is deprecated and layout incompatible with previous versions."
35+
" Use make_sender_adaptor(adaptor, args...) instead. "
36+
"The implementation now uses bound_sender_adaptor_closure, which stores the adaptor with "
37+
"[[no_unique_address]] and keeps bound arguments in product_type.")]] =
38+
bound_sender_adaptor_closure<std::decay_t<T>...>;
5339
} // namespace beman::execution::detail
5440

5541
// ----------------------------------------------------------------------------

include/beman/execution/detail/sender_adaptor_closure.hpp

Lines changed: 163 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,188 @@ import std;
1414
#endif
1515
#ifdef BEMAN_HAS_MODULES
1616
import beman.execution.detail.sender;
17+
import beman.execution.detail.call_result_t;
18+
import beman.execution.detail.callable;
19+
import beman.execution.detail.class_type;
20+
import beman.execution.detail.nothrow_callable;
21+
import beman.execution.detail.movable_value;
22+
import beman.execution.detail.product_type;
23+
1724
#else
1825
#include <beman/execution/detail/sender.hpp>
26+
#include <beman/execution/detail/class_type.hpp>
27+
#include <beman/execution/detail/call_result_t.hpp>
28+
#include <beman/execution/detail/callable.hpp>
29+
#include <beman/execution/detail/nothrow_callable.hpp>
30+
#include <beman/execution/detail/movable_value.hpp>
31+
#include <beman/execution/detail/product_type.hpp>
32+
1933
#endif
2034

2135
// ----------------------------------------------------------------------------
2236

2337
namespace beman::execution::detail::pipeable {
24-
struct sender_adaptor_closure_base {};
38+
/*!
39+
* \brief ADL anchor tag type inherited by sender_adaptor_closure.
40+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
41+
* \internal
42+
*/
43+
struct closure_t {};
2544
} // namespace beman::execution::detail::pipeable
2645

2746
namespace beman::execution {
28-
// NOLINTBEGIN(bugprone-crtp-constructor-accessibility)
29-
template <typename>
30-
struct sender_adaptor_closure : ::beman::execution::detail::pipeable::sender_adaptor_closure_base {};
31-
// NOLINTEND(bugprone-crtp-constructor-accessibility)
47+
/*!
48+
* \brief CRTP base class for pipeable sender adaptor closure objects.
49+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
50+
*/
51+
template <detail::class_type D>
52+
struct sender_adaptor_closure : detail::pipeable::closure_t {};
3253

3354
} // namespace beman::execution
3455

3556
namespace beman::execution::detail {
36-
template <typename Closure>
57+
58+
/*!
59+
* \brief Helper to detect a unique sender_adaptor_closure base class.
60+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
61+
* \internal
62+
*/
63+
template <class T>
64+
auto get_sender_adaptor_closure_base(const sender_adaptor_closure<T>&) -> T;
65+
66+
/*!
67+
* \brief Checks that T has exactly one sender_adaptor_closure base where U == decay_t<T>.
68+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
69+
* \internal
70+
*/
71+
template <class T>
72+
concept has_unique_sender_adaptor_closure_base = requires(const T& s) {
73+
{ get_sender_adaptor_closure_base(s) } -> std::same_as<std::decay_t<T>>;
74+
};
75+
76+
/*!
77+
* \brief Determine if a type is a pipeable sender adaptor closure.
78+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
79+
* \internal
80+
*/
81+
template <class T>
3782
concept is_sender_adaptor_closure =
38-
::std::derived_from<::std::decay_t<Closure>, ::beman::execution::sender_adaptor_closure<::std::decay_t<Closure>>>;
83+
std::derived_from<std::decay_t<T>, sender_adaptor_closure<std::decay_t<T>>> and
84+
has_unique_sender_adaptor_closure_base<std::decay_t<T>> and (not sender<std::decay_t<T>>);
85+
86+
/*!
87+
* \brief Checks that Closure is a pipeable sender adaptor closure invocable with Sender.
88+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
89+
* \internal
90+
*/
91+
template <class Closure, class Sender>
92+
concept sender_adaptor_closure_for =
93+
is_sender_adaptor_closure<Closure> and sender<Sender> and requires(Closure&& closure, Sender&& sndr) {
94+
{ std::forward<Closure>(closure)(std::forward<Sender>(sndr)) } -> sender;
95+
};
96+
97+
/*!
98+
* \brief Utility alias to copy cv-ref qualifiers from one type onto another.
99+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
100+
* \internal
101+
*/
102+
template <class As, class Reqs>
103+
using apply_cvref_t = decltype(std::forward_like<As>(std::declval<Reqs&>()));
104+
105+
/*!
106+
* \brief Perfect forwarding call wrapper produced by closure-closure composition via operator|.
107+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
108+
* \internal
109+
*/
110+
template <class Inner, class Outer>
111+
struct composed_sender_adaptor_closure : sender_adaptor_closure<composed_sender_adaptor_closure<Inner, Outer>> {
112+
[[no_unique_address]] Inner inner;
113+
[[no_unique_address]] Outer outer;
114+
115+
template <class Self, sender Sender>
116+
requires callable<apply_cvref_t<Self, Inner>, Sender> and
117+
callable<apply_cvref_t<Self, Outer>, call_result_t<apply_cvref_t<Self, Inner>, Sender>>
118+
constexpr auto operator()(this Self&& self, Sender&& sndr) noexcept(
119+
nothrow_callable<apply_cvref_t<Self, Inner>, Sender> and
120+
nothrow_callable<apply_cvref_t<Self, Outer>, call_result_t<apply_cvref_t<Self, Inner>, Sender>>)
121+
-> call_result_t<apply_cvref_t<Self, Outer>, call_result_t<apply_cvref_t<Self, Inner>, Sender>> {
122+
return std::forward_like<Self>(self.outer)(std::forward_like<Self>(self.inner)(std::forward<Sender>(sndr)));
123+
}
124+
};
125+
126+
// ctad
127+
template <class Inner, class Outer>
128+
composed_sender_adaptor_closure(Inner&&, Outer&&)
129+
-> composed_sender_adaptor_closure<std::decay_t<Inner>, std::decay_t<Outer>>;
130+
131+
/*!
132+
* \brief Perfect forwarding call wrapper produced by adaptor(args...) for multi-argument adaptors.
133+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
134+
* \internal
135+
*/
136+
template <class Adaptor, movable_value... BoundArgs>
137+
struct bound_sender_adaptor_closure : detail::product_type<std::decay_t<BoundArgs>...>,
138+
sender_adaptor_closure<bound_sender_adaptor_closure<Adaptor, BoundArgs...>> {
139+
140+
[[no_unique_address]] Adaptor adaptor;
141+
142+
template <class Self, sender Sender>
143+
requires callable<apply_cvref_t<Self, Adaptor>, Sender, apply_cvref_t<Self, BoundArgs>...>
144+
constexpr auto operator()(this Self&& self, Sender&& sndr) noexcept(
145+
nothrow_callable<apply_cvref_t<Self, Adaptor>, Sender, apply_cvref_t<Self, BoundArgs>...>)
146+
-> call_result_t<apply_cvref_t<Self, Adaptor>, Sender, apply_cvref_t<Self, BoundArgs>...> {
147+
return self.apply([&](auto&&... bound_args) {
148+
return std::forward_like<Self>(self.adaptor)(std::forward<Sender>(sndr),
149+
std::forward_like<Self>(bound_args)...);
150+
});
151+
}
152+
};
153+
154+
template <class Tag, class... Args>
155+
bound_sender_adaptor_closure(Tag&&, Args&&...)
156+
-> bound_sender_adaptor_closure<std::decay_t<Tag>, std::decay_t<Args>...>;
157+
158+
/*!
159+
* \brief Factory function producing a bound_sender_adaptor_closure from an adaptor and arguments.
160+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
161+
* \internal
162+
*/
163+
template <class Tag, class... Args>
164+
requires(movable_value<Args> && ...)
165+
constexpr auto
166+
make_sender_adaptor(Tag&& tag,
167+
Args&&... args) noexcept(std::is_nothrow_constructible_v<std::decay_t<Tag>, Tag> and
168+
(std::is_nothrow_constructible_v<std::decay_t<Args>, Args> and ...))
169+
-> bound_sender_adaptor_closure<std::decay_t<Tag>, std::decay_t<Args>...> {
170+
return {{std::forward<Args>(args)...}, {}, tag};
39171
}
172+
} // namespace beman::execution::detail
40173

41174
namespace beman::execution::detail::pipeable {
42-
template <::beman::execution::sender Sender, typename Adaptor>
43-
requires(!::beman::execution::sender<Adaptor>) &&
44-
::std::derived_from<::std::decay_t<Adaptor>,
45-
::beman::execution::sender_adaptor_closure<::std::decay_t<Adaptor>>> &&
46-
requires(Sender&& sender, Adaptor&& adaptor) {
47-
{ adaptor(::std::forward<Sender>(sender)) } -> ::beman::execution::sender;
48-
}
49-
auto operator|(Sender&& sender, Adaptor&& adaptor) {
50-
return adaptor(::std::forward<Sender>(sender));
175+
176+
/*!
177+
* \brief Pipe operator connecting a sender to a pipeable sender adaptor closure.
178+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
179+
*/
180+
template <sender Sender, detail::sender_adaptor_closure_for<Sender> Closure>
181+
constexpr auto operator|(Sender&& sndr, Closure&& cl) noexcept(detail::nothrow_callable<Closure, Sender>)
182+
-> detail::call_result_t<Closure, Sender> {
183+
return std::forward<Closure>(cl)(std::forward<Sender>(sndr));
184+
}
185+
186+
/*!
187+
* \brief Pipe operator composing two pipeable sender adaptor closure objects.
188+
* \headerfile beman/execution/execution.hpp <beman/execution/execution.hpp>
189+
*/
190+
template <detail::is_sender_adaptor_closure Inner, detail::is_sender_adaptor_closure Outer>
191+
requires std::constructible_from<std::decay_t<Inner>, Inner> && std::constructible_from<std::decay_t<Outer>, Outer>
192+
constexpr auto operator|(Inner&& inner,
193+
Outer&& outer) noexcept(std::is_nothrow_constructible_v<std::decay_t<Inner>, Inner> &&
194+
std::is_nothrow_constructible_v<std::decay_t<Outer>, Outer>)
195+
-> detail::composed_sender_adaptor_closure<std::decay_t<Inner>, std::decay_t<Outer>> {
196+
return {{}, std::forward<Inner>(inner), std::forward<Outer>(outer)};
51197
}
198+
52199
} // namespace beman::execution::detail::pipeable
53200

54201
// ----------------------------------------------------------------------------

0 commit comments

Comments
 (0)