33 Execution control library [exec]
33.9.11.2 execution::just, execution::just_error, execution::just_stopped [exec.just]
33.9.12.9 execution::then, execution::upon_error, execution::upon_stopped [exec.then]
33.9.12.10 execution::let_value, execution::let_error, execution::let_stopped [exec.let]
33.9.12.11 execution::bulk, execution::bulk_chunked, and execution::bulk_unchunked [exec.bulk]
Each algorithm has a default implementation
. Let
sndr be the result of an invocation of such an algorithm or
an object equal to the result (
[concepts.equality]), and
let
Sndr be
decltype((sndr)). Let
rcvr be a receiver of type
Rcvr
with associated environment
env of type
Env
such that
sender_to<Sndr, Rcvr> is
true. For the default implementation of the algorithm that produced
sndr,
connecting
sndr to
rcvr and
starting the resulting operation state (
[exec.async.ops])
necessarily results in the potential evaluation (
[basic.def.odr]) of
a set of completion operations
whose first argument is a subexpression equal to
rcvr. Let
Sigs be a pack of completion signatures corresponding to
this set of completion operations, and
let
CS be
the type of the expression
get_completion_signatures<Sndr, Env>(). Then
CS is
a specialization of
the class template
completion_signatures (
[exec.cmplsig]),
the set of whose template arguments is
Sigs. If none of the types in
Sigs are dependent on the type
Env, then
the expression
get_completion_signatures<Sndr>() is well-formed and
its type is
CS. If a user-provided implementation of the algorithm
that produced sndr is selected instead of the default:
Any completion signature
that is in the set of types
denoted by
completion_signatures_of_t<Sndr, Env> and
that is not part of
Sigs shall correspond to
error or stopped completion operations,
unless otherwise specified
.If none of the types in
Sigs are dependent on the type
Env, then
completion_signatures_of_t<Sndr> and
completion_signatures_of_t<Sndr, Env>
shall denote the same type
.
Various function templates in subclause [exec.
snd]
can throw an exception of type
unspecified-exception. Each such exception object is of an unspecified type
such that a
handler of type
exception
matches (
[except.handle]) the exception object
but a
handler of type
dependent_sender_error does not
. [
Note 1:
There is no requirement that two such exception objects have the same type
. —
end note]
Subclause [exec.
snd] makes use of the following exposition-only entities
.For a queryable object
env,
FWD-ENV(env) is an expression
whose type satisfies
queryable
such that for a query object
q and
a pack of subexpressions
as,
the expression
FWD-ENV(env).query(q, as...) is ill-formed
if
forwarding_query(q) is
false;
otherwise, it is expression-equivalent to
env.query(q, as...). The type
FWD-ENV-T(Env) is
decltype(FWD-ENV(declval<Env>())).For a query object
q and a subexpression
v,
MAKE-ENV(q, v) is an expression
env
whose type satisfies
queryable
such that the result of
env.query(q) has
a value equal to
v (
[concepts.equality])
. Unless otherwise stated,
the object to which
env.query(q) refers remains valid
while
env remains valid
.For two queryable objects
env1 and
env2,
a query object
q, and
a pack of subexpressions
as,
JOIN-ENV(env1, env2) is an expression
env3
whose type satisfies
queryable
such that
env3.query(q, as...) is expression-equivalent to:
- env1.query(q, as...) if that expression is well-formed,
- otherwise, env2.query(q, as...) if that expression is well-formed,
- otherwise, env3.query(q, as...) is ill-formed.
The results of
FWD-ENV,
MAKE-ENV, and
JOIN-ENV
can be context-dependent;
i.e., they can evaluate to expressions
with different types and value categories
in different contexts for the same arguments
.For a scheduler
sch,
SCHED-ATTRS(sch) is an expression
o1
whose type satisfies
queryable
such that
o1.query(get_completion_scheduler<Tag>) is
an expression with the same type and value as
sch
where
Tag is one of
set_value_t or
set_stopped_t, and
such that
o1.query(get_domain) is expression-equivalent to
sch.query(get_domain). SCHED-ENV(sch) is an expression
o2
whose type satisfies
queryable
such that
o2.query(get_scheduler) is a prvalue
with the same type and value as
sch, and
such that
o2.query(get_domain) is expression-equivalent to
sch.query(get_domain). For two subexpressions
rcvr and
expr,
SET-VALUE(rcvr, expr) is expression-equivalent to
(expr, set_value(std::move(rcvr)))
if the type of
expr is
void;
otherwise,
set_value(std::move(rcvr), expr). TRY-EVAL(rcvr, expr) is equivalent to:
try {
expr;
} catch(...) {
set_error(std::move(rcvr), current_exception());
}
if
expr is potentially-throwing; otherwise,
expr. TRY-SET-VALUE(rcvr, expr) is
TRY-EVAL(rcvr, SET-VALUE(rcvr, expr))
except that
rcvr is evaluated only once
. template<class Default = default_domain, class Sndr>
constexpr auto completion-domain(const Sndr& sndr) noexcept;
COMPL-DOMAIN(T) is the type of the expression
get_domain(get_completion_scheduler<T>(get_env(sndr))). Effects: If all of the types
COMPL-DOMAIN(set_value_t),
COMPL-DOMAIN(set_error_t), and
COMPL-DOMAIN(set_stopped_t) are ill-formed,
completion-domain<Default>(sndr) is
a default-constructed prvalue of type
Default. Otherwise, if they all share a common type (
[meta.trans.other])
(ignoring those types that are ill-formed),
then
completion-domain<Default>(sndr) is
a default-constructed prvalue of that type
. Otherwise,
completion-domain<Default>(sndr) is ill-formed
.template<class Tag, class Env, class Default>
constexpr decltype(auto) query-with-default(
Tag, const Env& env, Default&& value) noexcept(see below);
Let
e be the expression
Tag()(env)
if that expression is well-formed;
otherwise, it is
static_cast<Default>(std::forward<Default>(value)).Remarks: The expression in the noexcept clause is
noexcept(e). template<class Sndr>
constexpr auto get-domain-early(const Sndr& sndr) noexcept;
Effects: Equivalent to:
return Domain();
where
Domain is
the decayed type of the first of the following expressions that is well-formed:
- get_domain(get_env(sndr))
- completion-domain(sndr)
- default_domain()
template<class Sndr, class Env>
constexpr auto get-domain-late(const Sndr& sndr, const Env& env) noexcept;
Effects: Equivalent to:
- If sender-for<Sndr, continues_on_t> is true, then
return Domain();
where Domain is the type of the following expression:
[] {
auto [_, sch, _] = sndr;
return query-with-default(get_domain, sch, default_domain());
}();
[
Note 1:
The
continues_on algorithm works
in tandem with
schedule_from (
[exec.schedule.from])
to give scheduler authors a way to customize both
how to transition onto (
continues_on) and off of (
schedule_from)
a given execution context
. Thus,
continues_on ignores the domain of the predecessor and
uses the domain of the destination scheduler to select a customization,
a property that is unique to
continues_on. That is why it is given special treatment here
. —
end note]
- Otherwise,
return Domain();
where Domain is the first of the following expressions
that is well-formed and whose type is not void:
- get_domain(get_env(sndr))
- completion-domain<void>(sndr)
- get_domain(env)
- get_domain(get_scheduler(env))
- default_domain()
template<callable Fun>
requires is_nothrow_move_constructible_v<Fun>
struct emplace-from {
Fun fun;
using type = call-result-t<Fun>;
constexpr operator type() && noexcept(nothrow-callable<Fun>) {
return std::move(fun)();
}
constexpr type operator()() && noexcept(nothrow-callable<Fun>) {
return std::move(fun)();
}
};
[
Note 2:
emplace-from is used to emplace non-movable types
into
tuple,
optional,
variant, and similar types
. —
end note]
struct on-stop-request {
inplace_stop_source& stop-src;
void operator()() noexcept { stop-src.request_stop(); }
};
template<class T0, class T1, …, class Tn>
struct product-type {
T0 t0;
T1 t1;
⋮
Tn tn;
template<size_t I, class Self>
constexpr decltype(auto) get(this Self&& self) noexcept;
template<class Self, class Fn>
constexpr decltype(auto) apply(this Self&& self, Fn&& fn)
noexcept(see below);
};
[
Note 3:
product-type is presented here in pseudo-code form
for the sake of exposition
. It can be approximated in standard C++ with a tuple-like implementation
that takes care to keep the type an aggregate
that can be used as the initializer of a structured binding declaration
. —
end note]
[
Note 4:
An expression of type
product-type is usable as
the initializer of a structured binding declaration (
[dcl.struct.bind])
. —
end note]
template<size_t I, class Self>
constexpr decltype(auto) get(this Self&& self) noexcept;
Effects: Equivalent to:
auto& [...ts] = self;
return std::forward_like<Self>(ts...[I]);
template<class Self, class Fn>
constexpr decltype(auto) apply(this Self&& self, Fn&& fn) noexcept(see below);
Constraints: The expression in the
return statement below is well-formed
. Effects: Equivalent to:
auto& [...ts] = self;
return std::forward<Fn>(fn)(std::forward_like<Self>(ts)...);
Remarks: The expression in the
noexcept clause is
true
if the
return statement above is not potentially throwing;
otherwise,
false. template<class Tag, class Data = see below, class... Child>
constexpr auto make-sender(Tag tag, Data&& data, Child&&... child);
Mandates: The following expressions are
true:
- semiregular<Tag>
- movable-value<Data>
- (sender<Child> && ...)
- dependent_sender<Sndr> || sender_in<Sndr>,
where Sndr is
basic-sender<Tag, decay_t<Data>, decay_t<Child>...>
as defined below.
Recommended practice: If evaluation of
sender_in<Sndr> results in
an uncaught exception from
the evaluation of
get_completion_signatures<Sndr>(),
the implementation should include information about that exception in
the resulting diagnostic
.
Returns: A prvalue of
type
basic-sender<Tag, decay_t<Data>, decay_t<Child>...>
that has been direct-list-initialized with the forwarded arguments,
where
basic-sender is the following exposition-only class template except as noted below
. Remarks: The default template argument for the
Data template parameter
denotes an unspecified empty trivially copyable class type
that models
semiregular. namespace std::execution {
template<class Sndr, class Rcvr>
using state-type = decay_t<call-result-t<
decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>>;
template<class Index, class Sndr, class Rcvr>
using env-type = call-result-t<
decltype(impls-for<tag_of_t<Sndr>>::get-env), Index,
state-type<Sndr, Rcvr>&, const Rcvr&>;
template<class Sndr>
using data-type = decltype(declval<Sndr>().template get<1>());
template<class Sndr, size_t I = 0>
using child-type = decltype(declval<Sndr>().template get<I+2>());
template<class Sndr>
using indices-for = remove_reference_t<Sndr>::indices-for;
template<class Sndr, class Rcvr>
struct basic-state {
basic-state(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below)
: rcvr(std::move(rcvr))
, state(impls-for<tag_of_t<Sndr>>::get-state(std::forward<Sndr>(sndr), rcvr)) { }
Rcvr rcvr;
state-type<Sndr, Rcvr> state;
};
}
The expression in the
noexcept clause of
the constructor of
basic-state is
is_nothrow_move_constructible_v<Rcvr> &&
nothrow-callable<decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&> &&
(same_as<state-type<Sndr, Rcvr>, get-state-result> ||
is_nothrow_constructible_v<state-type<Sndr, Rcvr>, get-state-result>)
where
get-state-result is
call-result-t<decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>.
namespace std::execution {
template<class Sndr, class Rcvr, class Index>
requires valid-specialization<env-type, Index, Sndr, Rcvr>
struct basic-receiver {
using receiver_concept = receiver_t;
using tag-t = tag_of_t<Sndr>;
using state-t = state-type<Sndr, Rcvr>;
static constexpr const auto& complete = impls-for<tag-t>::complete;
template<class... Args>
requires callable<decltype(complete), Index, state-t&, Rcvr&, set_value_t, Args...>
void set_value(Args&&... args) && noexcept {
complete(Index(), op->state, op->rcvr, set_value_t(), std::forward<Args>(args)...);
}
template<class Error>
requires callable<decltype(complete), Index, state-t&, Rcvr&, set_error_t, Error>
void set_error(Error&& err) && noexcept {
complete(Index(), op->state, op->rcvr, set_error_t(), std::forward<Error>(err));
}
void set_stopped() && noexcept
requires callable<decltype(complete), Index, state-t&, Rcvr&, set_stopped_t> {
complete(Index(), op->state, op->rcvr, set_stopped_t());
}
auto get_env() const noexcept -> env-type<Index, Sndr, Rcvr> {
return impls-for<tag-t>::get-env(Index(), op->state, op->rcvr);
}
basic-state<Sndr, Rcvr>* op;
};
}
namespace std::execution {
constexpr auto connect-all = see below;
template<class Sndr, class Rcvr>
using connect-all-result = call-result-t<
decltype(connect-all), basic-state<Sndr, Rcvr>*, Sndr, indices-for<Sndr>>;
}
The object connect-all is initialized with
a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr, size_t... Is>(
basic-state<Sndr, Rcvr>* op, Sndr&& sndr, index_sequence<Is...>) noexcept(see below)
-> decltype(auto) {
auto& [_, data, ...child] = sndr;
return product-type{connect(
std::forward_like<Sndr>(child),
basic-receiver<Sndr, Rcvr, integral_constant<size_t, Is>>{op})...};
}
Constraints: The expression in the
return statement is well-formed
. Remarks: The expression in the
noexcept clause is
true
if the
return statement is not potentially throwing;
otherwise,
false. namespace std::execution {
template<class Sndr, class Rcvr>
requires valid-specialization<state-type, Sndr, Rcvr> &&
valid-specialization<connect-all-result, Sndr, Rcvr>
struct basic-operation : basic-state<Sndr, Rcvr> {
using operation_state_concept = operation_state_t;
using tag-t = tag_of_t<Sndr>;
connect-all-result<Sndr, Rcvr> inner-ops;
basic-operation(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below)
: basic-state<Sndr, Rcvr>(std::forward<Sndr>(sndr), std::move(rcvr)),
inner-ops(connect-all(this, std::forward<Sndr>(sndr), indices-for<Sndr>()))
{}
void start() & noexcept {
auto& [...ops] = inner-ops;
impls-for<tag-t>::start(this->state, this->rcvr, ops...);
}
};
}
The expression in the noexcept clause of
the constructor of basic-operation is:
is_nothrow_constructible_v<basic-state<Self, Rcvr>, Self, Rcvr> &&
noexcept(connect-all(this, std::forward<Sndr>(sndr), indices-for<Sndr>()))
namespace std::execution {
struct default-impls {
static constexpr auto get-attrs = see below;
static constexpr auto get-env = see below;
static constexpr auto get-state = see below;
static constexpr auto start = see below;
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
template<class Tag>
struct impls-for : default-impls {};
}
The member default-impls::get-attrs
is initialized with a callable object equivalent to the following lambda:
[](const auto&, const auto&... child) noexcept -> decltype(auto) {
if constexpr (sizeof...(child) == 1)
return (FWD-ENV(get_env(child)), ...);
else
return env<>();
}
The member default-impls::get-env
is initialized with a callable object equivalent to the following lambda:
[](auto, auto&, const auto& rcvr) noexcept -> decltype(auto) {
return FWD-ENV(get_env(rcvr));
}
The member default-impls::get-state
is initialized with a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) {
auto& [_, data, ...child] = sndr;
return allocator-aware-forward(std::forward_like<Sndr>(data), rcvr);
}
The member default-impls::start
is initialized with a callable object equivalent to the following lambda:
[](auto&, auto&, auto&... ops) noexcept -> void {
(execution::start(ops), ...);
}
The member
default-impls::complete
is initialized with a callable object equivalent to the following lambda:
[]<class Index, class Rcvr, class Tag, class... Args>(
Index, auto& state, Rcvr& rcvr, Tag, Args&&... args) noexcept
-> void requires callable<Tag, Rcvr, Args...> {
static_assert(Index::value == 0);
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}
template<class Sndr, class... Env>
static consteval void default-impls::check-types();
Let
Is be the pack of integral template arguments of
the
integer_sequence specialization denoted by
indices-for<Sndr>.Effects: Equivalent to:
((void)get_completion_signatures<child-type<Sndr, Is>, FWD-ENV-T(Env)...>(), ...);
[
Note 5:
For any types
T and
S, and pack
E,
let
e be the expression
impls-for<T>::check-types<S, E...>(). Then exactly one of the following is
true:
- e is ill-formed, or
- the evaluation of e exits with an exception, or
- e is a core constant expression.
When
e is a core constant expression,
the pack
S, E... uniquely determines a set of completion signatures
. —
end note]
namespace std::execution {
template<class Tag, class Data, class... Child>
struct basic-sender : product-type<Tag, Data, Child...> {
using sender_concept = sender_t;
using indices-for = index_sequence_for<Child...>;
decltype(auto) get_env() const noexcept {
auto& [_, data, ...child] = *this;
return impls-for<Tag>::get-attrs(data, child...);
}
template<decays-to<basic-sender> Self, receiver Rcvr>
auto connect(this Self&& self, Rcvr rcvr) noexcept(see below)
-> basic-operation<Self, Rcvr> {
return {std::forward<Self>(self), std::move(rcvr)};
}
template<decays-to<basic-sender> Self, class... Env>
static constexpr auto get_completion_signatures();
};
}
It is unspecified whether a specialization of
basic-sender
is an aggregate
.An expression of type
basic-sender is usable as
the initializer of a structured binding declaration (
[dcl.struct.bind])
.The expression in the noexcept clause of
the connect member function of basic-sender is:
is_nothrow_constructible_v<basic-operation<Self, Rcvr>, Self, Rcvr>
template<class Tag, class Data, class... Child>
template<decays-to<basic-sender> Sndr, class... Env>
constexpr auto basic-sender<Tag, Data, Child...>::get_completion_signatures();
Let
Rcvr be the type of a receiver whose
environment has type
E, where
E is the first type in the list
Env..., env<>. Let CHECK-TYPES() be the expression
impls-for<Tag>::template check-types<
Sndr, E>(), and
let CS be a type determined as follows:
If
CHECK-TYPES() is a core constant expression,
let
op be an lvalue subexpression
whose type is
connect_result_t<Sndr, Rcvr>. Then
CS is the specialization of
completion_signatures
the set of whose template arguments
correspond to the set of completion operations
that are potentially evaluated (
[basic.def.odr])
as a result of evaluating
op.start().Otherwise,
CS is
completion_signatures<>.
Constraints:
CHECK-TYPES() is a well-formed expression
. Effects: Equivalent to:
CHECK-TYPES();
return CS();
template<class... Fns>
struct overload-set : Fns... {
using Fns::operator()...;
};
struct not-a-sender {
using sender_concept = sender_t;
template<class Sndr>
static consteval auto get_completion_signatures() -> completion_signatures<> {
throw unspecified-exception();
}
};
constexpr void decay-copyable-result-datums(auto cs) {
cs.for-each([]<class Tag, class... Ts>(Tag(*)(Ts...)) {
if constexpr (!(is_constructible_v<decay_t<Ts>, Ts> &&...))
throw unspecified-exception();
});
}
template<class T, class Context>
decltype(auto) allocator-aware-forward(T&& obj, Context&& context);
allocator-aware-forward is an exposition-only function template
used to either
create a new object of type
remove_cvref_t<T> from
obj
or forward
obj depending on whether an allocator is available
. If the environment associated with
context provides an allocator
(i.e., the expression
get_allocator(get_env(context)) is valid),
let
alloc be the result of this expression
and let
P be
remove_cvref_t<T>.Returns:
- If alloc is not defined, returns std::forward<T>(obj),
- otherwise if P is a specialization of product-type,
returns an object of type P whose elements are initialized using
make_obj_using_allocator<decltype(e)>(alloc, std::forward_like<T>(e))
where e is the corresponding element of obj,
- otherwise, returns make_obj_using_allocator<P>(alloc, std::forward<T>(obj)).
The
sender_in concept defines
the requirements for a sender type
that can create asynchronous operations given an associated environment type
. The
sender_to concept defines
the requirements for a sender type
that can connect with a specific receiver type
. The
get_env customization point object is used to access
a sender's associated attributes
. The connect customization point object is used to connect (
[exec.async.ops])
a sender and a receiver to produce an operation state
. namespace std::execution {
template<auto>
concept is-constant = true;
template<class Sndr>
concept is-sender =
derived_from<typename Sndr::sender_concept, sender_t>;
template<class Sndr>
concept enable-sender =
is-sender<Sndr> ||
is-awaitable<Sndr, env-promise<env<>>>;
template<class Sndr>
inline constexpr bool enable_sender = enable-sender<Sndr>;
template<class Sndr>
consteval bool is-dependent-sender-helper() try {
get_completion_signatures<Sndr>();
return false;
} catch (dependent_sender_error&) {
return true;
}
template<class Sndr>
concept sender =
enable_sender<remove_cvref_t<Sndr>> &&
requires (const remove_cvref_t<Sndr>& sndr) {
{ get_env(sndr) } -> queryable;
} &&
move_constructible<remove_cvref_t<Sndr>> &&
constructible_from<remove_cvref_t<Sndr>, Sndr>;
template<class Sndr, class... Env>
concept sender_in =
sender<Sndr> &&
(sizeof...(Env) <= 1) &&
(queryable<Env> &&...) &&
is-constant<get_completion_signatures<Sndr, Env...>()>;
template<class Sndr>
concept dependent_sender =
sender<Sndr> && bool_constant<is-dependent-sender-helper<Sndr>()>::value;
template<class Sndr, class Rcvr>
concept sender_to =
sender_in<Sndr, env_of_t<Rcvr>> &&
receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>> &&
requires (Sndr&& sndr, Rcvr&& rcvr) {
connect(std::forward<Sndr>(sndr), std::forward<Rcvr>(rcvr));
};
}
Given a subexpression
sndr,
let
Sndr be
decltype((sndr)) and
let
rcvr be a receiver
with an associated environment whose type is
Env. A completion operation is a
permissible completion
for
Sndr and
Env
if its completion signature appears in the argument list of the specialization of
completion_signatures denoted by
completion_signatures_of_t<Sndr, Env>. Sndr and
Env model
sender_in<Sndr, Env>
if all the completion operations
that are potentially evaluated by connecting
sndr to
rcvr and
starting the resulting operation state
are permissible completions for
Sndr and
Env. Remarks: Pursuant to
[namespace.std],
users may specialize
enable_sender to
true for cv-unqualified program-defined types that
model
sender, and
false for types that do not
. Such specializations shall
be usable in constant expressions (
[expr.const.init]) and
have type
const bool.The exposition-only concepts
sender-of and
sender-in-of
define the requirements for a sender type
that completes with a given unique set of value result types
. namespace std::execution {
template<class... As>
using value-signature = set_value_t(As...);
template<class Sndr, class SetValue, class... Env>
concept sender-in-of-impl =
sender_in<Sndr, Env...> &&
MATCHING-SIG(SetValue,
gather-signatures<set_value_t,
completion_signatures_of_t<Sndr, Env...>,
value-signature,
type_identity_t>);
template<class Sndr, class Env, class... Values>
concept sender-in-of =
sender-in-of-impl<Sndr, set_value_t(Values...), Env>;
template<class Sndr, class... Values>
concept sender-of =
sender-in-of-impl<Sndr, set_value_t(Values...)>;
}
Let
sndr be an expression
such that
decltype((sndr)) is
Sndr. The type
tag_of_t<Sndr> is as follows:
- If the declaration
auto&& [tag, data, ...children] = sndr;
would be well-formed, tag_of_t<Sndr> is
an alias for decltype(auto(tag)).
- Otherwise, tag_of_t<Sndr> is ill-formed.
Let
sender-for be an exposition-only concept defined as follows:
namespace std::execution {
template<class Sndr, class Tag>
concept sender-for =
sender<Sndr> &&
same_as<tag_of_t<Sndr>, Tag>;
}
For a type
T,
SET-VALUE-SIG(T) denotes the type
set_value_t()
if
T is
cv void;
otherwise, it denotes the type
set_value_t(T).Library-provided sender types
- always expose an overload of a member connect
that accepts an rvalue sender and
- only expose an overload of a member connect
that accepts an lvalue sender if they model copy_constructible.
The sender concepts recognize awaitables as senders
. For
[exec], an
awaitable is an expression
that would be well-formed as the operand of a
co_await expression
within a given context
.For a subexpression
c,
let
GET-AWAITER(c, p) be expression-equivalent to
the series of transformations and conversions applied to
c
as the operand of an
await-expression in a coroutine,
resulting in lvalue
e as described by
[expr.await],
where
p is an lvalue referring to the coroutine's promise,
which has type
Promise. [
Note 1:
This includes the invocation of
the promise type's
await_transform member if any,
the invocation of the
operator co_await
picked by overload resolution if any, and
any necessary implicit conversions and materializations
. —
end note]
Let
GET-AWAITER(c) be
expression-equivalent to
GET-AWAITER(c, q)
where
q is an lvalue of
an unspecified empty class type
none-such that
lacks an
await_transform member, and
where
coroutine_handle<none-such> behaves as
coroutine_handle<void>.Let
is-awaitable be the following exposition-only concept:
namespace std {
template<class T>
concept await-suspend-result = see below;
template<class A, class... Promise>
concept is-awaiter =
requires (A& a, coroutine_handle<Promise...> h) {
a.await_ready() ? 1 : 0;
{ a.await_suspend(h) } -> await-suspend-result;
a.await_resume();
};
template<class C, class... Promise>
concept is-awaitable =
requires (C (*fc)() noexcept, Promise&... p) {
{ GET-AWAITER(fc(), p...) } -> is-awaiter<Promise...>;
};
}
await-suspend-result<T> is
true
if and only if one of the following is
true:
- T is void, or
- T is bool, or
- T is a specialization of coroutine_handle.
For a subexpression
c
such that
decltype((c)) is type
C, and
an lvalue
p of type
Promise,
await-result-
type<C, Promise> denotes
the type
decltype(GET-AWAITER(c, p).await_resume()) and
await-result-type<C> denotes
the type
decltype(GET-AWAITER(c).await_resume()).Let
with-await-transform be the exposition-only class template:
namespace std::execution {
template<class T, class Promise>
concept has-as-awaitable =
requires (T&& t, Promise& p) {
{ std::forward<T>(t).as_awaitable(p) } -> is-awaitable<Promise&>;
};
template<class Derived>
struct with-await-transform {
template<class T>
T&& await_transform(T&& value) noexcept {
return std::forward<T>(value);
}
template<has-as-awaitable<Derived> T>
auto await_transform(T&& value)
noexcept(noexcept(std::forward<T>(value).as_awaitable(declval<Derived&>())))
-> decltype(std::forward<T>(value).as_awaitable(declval<Derived&>())) {
return std::forward<T>(value).as_awaitable(static_cast<Derived&>(*this));
}
};
}
Let
env-promise be the exposition-only class template:
namespace std::execution {
template<class Env>
struct env-promise : with-await-transform<env-promise<Env>> {
unspecified get_return_object() noexcept;
unspecified initial_suspend() noexcept;
unspecified final_suspend() noexcept;
void unhandled_exception() noexcept;
void return_void() noexcept;
coroutine_handle<> unhandled_stopped() noexcept;
const Env& get_env() const noexcept;
};
}
[
Note 2:
Specializations of
env-promise are used only for the purpose of type computation;
its members need not be defined
. —
end note]
namespace std::execution {
struct default_domain {
template<sender Sndr, queryable... Env>
requires (sizeof...(Env) <= 1)
static constexpr sender decltype(auto) transform_sender(Sndr&& sndr, const Env&... env)
noexcept(see below);
template<sender Sndr, queryable Env>
static constexpr queryable decltype(auto) transform_env(Sndr&& sndr, Env&& env) noexcept;
template<class Tag, sender Sndr, class... Args>
static constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args)
noexcept(see below);
};
}
template<sender Sndr, queryable... Env>
requires (sizeof...(Env) <= 1)
constexpr sender decltype(auto) transform_sender(Sndr&& sndr, const Env&... env)
noexcept(see below);
Let
e be the expression
tag_of_t<Sndr>().transform_sender(std::forward<Sndr>(sndr), env...)
if that expression is well-formed;
otherwise,
std::forward<Sndr>(sndr).Remarks: The exception specification is equivalent to
noexcept(e). Let
e be the expression
tag_of_t<Sndr>().transform_env(std::forward<Sndr>(sndr), std::forward<Env>(env))
if that expression is well-formed;
otherwise,
FWD-ENV(std::forward<Env>(env)).Mandates:
noexcept(e) is
true. template<class Tag, sender Sndr, class... Args>
constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args)
noexcept(see below);
Let e be the expression
Tag().apply_sender(std::forward<Sndr>(sndr), std::forward<Args>(args)...)
Constraints:
e is a well-formed expression
. Remarks: The exception specification is equivalent to
noexcept(e). namespace std::execution {
template<class Domain, class Tag, sender Sndr, class... Args>
constexpr decltype(auto) apply_sender(Domain dom, Tag, Sndr&& sndr, Args&&... args)
noexcept(see below);
}
Let e be the expression
dom.apply_sender(Tag(), std::forward<Sndr>(sndr), std::forward<Args>(args)...)
if that expression is well-formed; otherwise,
default_domain().apply_sender(Tag(), std::forward<Sndr>(sndr), std::forward<Args>(args)...)
Constraints: The expression
e is well-formed
. Remarks: The exception specification is equivalent to
noexcept(e). Let
CHECKED-COMPLSIGS(e) be
e
if
e is a core constant expression whose
type satisfies
valid-completion-signatures;
otherwise, it is the following expression:
(e, throw except, completion_signatures())
Let
get-complsigs<Sndr, Env...>()
be expression-equivalent to
remove_reference_t<Sndr>::template get_completion_signatures<Sndr, Env...>(). Let NewSndr be Sndr
if sizeof...(Env) == 0 is true;
otherwise, decltype(s)
where s is the following expression:
transform_sender(
get-domain-late(declval<Sndr>(), declval<Env>()...),
declval<Sndr>(),
declval<Env>()...)
Constraints:
sizeof...(Env) <= 1 is
true. Effects: Equivalent to:
return e;
where
e is expression-equivalent to the following:
CHECKED-COMPLSIGS(get-complsigs<NewSndr, Env...>())
if
get-complsigs<NewSndr, Env
...>()
is a well-formed expression
. Otherwise,
CHECKED-COMPLSIGS(get-complsigs<NewSndr>())
if
get-complsigs<NewSndr>()
is a well-formed expression
.Otherwise,
completion_signatures<
SET-VALUE-SIG(await-result-type<NewSndr, env-promise<Env>...>),
set_error_t(exception_ptr),
set_stopped_t()>
if
is-awaitable<NewSndr, env-promise<Env>...>
is
true.Otherwise,
(throw dependent-sender-error(), completion_signatures())
if
sizeof...(
Env) == 0 is
true,
where
dependent-sender-error is
dependent_sender_error or
an unspecified type derived publicly and unambiguously from
dependent_sender_error.Otherwise,
(throw except, completion_signatures()).
Given a type
Env, if
completion_signatures_of_t<Sndr> and
completion_signatures_of_t<Sndr, Env>
are both well-formed,
they shall denote the same type
.Let
rcvr be an rvalue
whose type
Rcvr models
receiver, and
let
Sndr be the type of a sender
such that
sender_in<Sndr, env_of_t<Rcvr>> is
true. Let
Sigs... be the template arguments of
the
completion_signatures specialization
named by
completion_signatures_of_t<Sndr, env_of_t<Rcvr>>. Let
CSO be a completion function
. If sender
Sndr or its operation state cause
the expression
CSO(rcvr, args...)
to be potentially evaluated (
[basic.def.odr])
then there shall be a signature
Sig in
Sigs...
such that
MATCHING-SIG(decayed-typeof<CSO>(decltype(args)...), Sig)
is
true (
[exec.general])
.The name
connect denotes a customization point object
. For subexpressions
sndr and
rcvr,
let
Sndr be
decltype((sndr)) and
Rcvr be
decltype((rcvr)),
let
new_sndr be the expression
transform_sender(decltype(get-domain-late(sndr, get_env(rcvr))){}, sndr, get_env(rcvr))
and let
DS and
DR be
decay_t<decltype((new_sndr))> and
decay_t<Rcvr>, respectively
.Let connect-awaitable-promise be the following exposition-only class:
namespace std::execution {
struct connect-awaitable-promise : with-await-transform<connect-awaitable-promise> {
connect-awaitable-promise(DS&, DR& rcvr) noexcept : rcvr(rcvr) {}
suspend_always initial_suspend() noexcept { return {}; }
[[noreturn]] suspend_always final_suspend() noexcept { terminate(); }
[[noreturn]] void unhandled_exception() noexcept { terminate(); }
[[noreturn]] void return_void() noexcept { terminate(); }
coroutine_handle<> unhandled_stopped() noexcept {
set_stopped(std::move(rcvr));
return noop_coroutine();
}
operation-state-task get_return_object() noexcept {
return operation-state-task{
coroutine_handle<connect-awaitable-promise>::from_promise(*this)};
}
env_of_t<DR> get_env() const noexcept {
return execution::get_env(rcvr);
}
private:
DR& rcvr;
};
}
Let operation-state-task be the following exposition-only class:
namespace std::execution {
struct operation-state-task {
using operation_state_concept = operation_state_t;
using promise_type = connect-awaitable-promise;
explicit operation-state-task(coroutine_handle<> h) noexcept : coro(h) {}
operation-state-task(operation-state-task&&) = delete;
~operation-state-task() { coro.destroy(); }
void start() & noexcept {
coro.resume();
}
private:
coroutine_handle<> coro;
};
}
Let
V name the type
await-result-type<DS, connect-awaitable-promise>,
let
Sigs name the type
completion_signatures<
SET-VALUE-SIG(V),
set_error_t(exception_ptr),
set_stopped_t()>
and let
connect-awaitable be an exposition-only coroutine
defined as follows:
namespace std::execution {
template<class Fun, class... Ts>
auto suspend-complete(Fun fun, Ts&&... as) noexcept {
auto fn = [&, fun]() noexcept { fun(std::forward<Ts>(as)...); };
struct awaiter {
decltype(fn) fn;
static constexpr bool await_ready() noexcept { return false; }
void await_suspend(coroutine_handle<>) noexcept { fn(); }
[[noreturn]] void await_resume() noexcept { unreachable(); }
};
return awaiter{fn};
}
operation-state-task connect-awaitable(DS sndr, DR rcvr) requires receiver_of<DR, Sigs> {
exception_ptr ep;
try {
if constexpr (same_as<V, void>) {
co_await std::move(sndr);
co_await suspend-complete(set_value, std::move(rcvr));
} else {
co_await suspend-complete(set_value, std::move(rcvr), co_await std::move(sndr));
}
} catch(...) {
ep = current_exception();
}
co_await suspend-complete(set_error, std::move(rcvr), std::move(ep));
}
}
The expression
connect(sndr, rcvr) is expression-equivalent to:
new_sndr.connect(rcvr) if that expression is well-formed
. Otherwise,
connect-awaitable(new_sndr, rcvr).
Except that
rcvr is evaluated only once
. The program is ill-formed, no diagnostic required,
if there exists an rvalue expression
rcvr2 such that:
- decltype(rcvr2) models receiver,
- noexcept(rcvr2) is true,
- is_same_v<decltype(get_env(rcvr2)), decltype(get_env(rcvr))> is true,
- noexcept(execution::connect(sndr, rcvr)) is true, and
- noexcept(execution::connect(sndr, rcvr2))
is well-formed and evaluates to false.
[
Note 1:
This allows determination of whether
connect throws
with only the context of the environment,
such as within
get_completion_signatures. —
end note]
Mandates: The following are
true:
The name
schedule denotes a customization point object
. For a subexpression
sch,
the expression
schedule(sch) is expression-equivalent to
sch.schedule().Mandates: The type of
sch.schedule() satisfies
sender. If the expression
get_completion_scheduler<set_value_t>(get_env(sch.schedule())) == sch
is ill-formed or evaluates to
false,
the behavior of calling
schedule(sch) is undefined
.just,
just_error, and
just_stopped are sender factories
whose asynchronous operations complete synchronously in their start operation
with a value completion operation,
an error completion operation, or
a stopped completion operation, respectively
. The names
just,
just_error, and
just_stopped denote
customization point objects
. Let
just-cpo be one of
just,
just_error, or
just_stopped. For a pack of subexpressions
ts,
let
Ts be the pack of types
decltype((ts)). The expression
just-cpo(ts...) is ill-formed if
- (movable-value<Ts> &&...) is false, or
- just-cpo is just_error and
sizeof...(ts) == 1 is false, or
- just-cpo is just_stopped and
sizeof...(ts) == 0 is false.
Otherwise, it is expression-equivalent to
make-sender(just-cpo, product-type{ts...}).For
just,
just_error, and
just_stopped,
let
set-cpo be
set_value,
set_error, and
set_stopped, respectively
. The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
just-cpo as follows:
namespace std::execution {
template<>
struct impls-for<decayed-typeof<just-cpo>> : default-impls {
static constexpr auto start =
[](auto& state, auto& rcvr) noexcept -> void {
auto& [...ts] = state;
set-cpo(std::move(rcvr), std::move(ts)...);
};
};
}
read_env is a sender factory for a sender
whose asynchronous operation completes synchronously in its start operation
with a value completion result equal to
a value read from the receiver's associated environment
. read_env is a customization point object
. For some query object
q,
the expression
read_env(q) is expression-equivalent to
make-sender(read_env, q).The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
read_env as follows:
namespace std::execution {
template<>
struct impls-for<decayed-typeof<read_env>> : default-impls {
static constexpr auto start =
[](auto query, auto& rcvr) noexcept -> void {
TRY-SET-VALUE(rcvr, query(get_env(rcvr)));
};
};
template<class Sndr, class Env>
static consteval void check-types();
}
template<class Sndr, class Env>
static consteval void check-types();
Let
Q be
decay_t<data-type<Sndr>>.Throws: An exception of type
unspecified-exception (
[exec.snd.general]) if
the expression
Q()(env) is ill-formed or has type
void, where
env is an lvalue subexpression whose type is
Env. The bitwise inclusive
or operator is overloaded
for the purpose of creating sender chains
. The adaptors also support function call syntax with equivalent semantics
.Unless otherwise specified:
A sender adaptor is prohibited from causing observable effects,
apart from moving and copying its arguments,
before the returned sender is connected with a receiver using
connect,
and
start is called on the resulting operation state
.A parent sender with more than one child sender has
an associated attributes object equal to
env<>{}.When a parent sender is connected to a receiver
rcvr,
any receiver used to connect a child sender has
an associated environment equal to
FWD-ENV(get_env(rcvr)).An adaptor whose child senders are all non-dependent (
[exec.async.ops])
is itself non-dependent
.These requirements apply to any function
that is selected by the implementation of the sender adaptor
.Recommended practice: Implementations should use
the completion signatures of the adaptors
to communicate type errors to users and
to propagate any such type errors from child senders
.
If a sender returned from a sender adaptor specified in
[exec.adapt]
is specified to include
set_error_t(Err)
among its set of completion signatures
where
decay_t<Err> denotes the type
exception_ptr,
but the implementation does not potentially evaluate
an error completion operation with an
exception_ptr argument,
the implementation is allowed to omit
the
exception_ptr error completion signature from the set
. For a pipeable sender adaptor closure object
c and
an expression
sndr
such that
decltype((sndr)) models
sender,
the following expressions are equivalent and yield a
sender:
c(sndr)
sndr | c
Given an additional pipeable sender adaptor closure object d,
the expression c | d produces
another pipeable sender adaptor closure object e:
e is a perfect forwarding call wrapper (
[func.require])
with the following properties:
Its target object is an object
d2 of type
decltype(auto(d))
direct-non-list-initialized with
d.It has one bound argument entity,
an object
c2 of type
decltype(auto(c))
direct-non-list-initialized with
c.Its call pattern is
d2(c2(arg)),
where
arg is the argument used in a function call expression of
e.
The expression
c | d is well-formed if and only if
the initializations of the state entities (
[func.def]) of
e
are all well-formed
.An object
t of type
T is
a pipeable sender adaptor closure object
if
T models
derived_from<sender_adaptor_closure<T>>,
T has no other base classes
of type
sender_adaptor_closure<U> for any other type
U, and
T does not satisfy
sender.The template parameter
D for
sender_adaptor_closure can be
an incomplete type
. Before any expression of type
cv D appears as
an operand to the
| operator,
D shall be complete and
model
derived_from<sender_adaptor_closure<D>>. The behavior of an expression involving an object of type
cv D
as an operand to the
| operator is undefined
if overload resolution selects a program-defined
operator| function
. If a pipeable sender adaptor object accepts only one argument,
then it is a pipeable sender adaptor closure object
. If a pipeable sender adaptor object adaptor accepts more than one argument,
then let
sndr be an expression
such that
decltype((sndr)) models
sender,
let
args... be arguments
such that
adaptor(sndr, args...) is a well-formed expression
as specified below, and
let
BoundArgs be a pack that denotes
decltype(auto(args)).... The expression
adaptor(args...) produces
a pipeable sender adaptor closure object
f
that is a perfect forwarding call wrapper with the following properties:
Its target object is a copy of adaptor
.Its bound argument entities
bound_args consist of
objects of types
BoundArgs... direct-non-list-initialized with
std::forward<decltype((args))>(args)..., respectively
.Its call pattern is
adaptor(rcvr, bound_args...),
where
rcvr is
the argument used in a function call expression of
f.
The expression
adaptor(args...) is well-formed if and only if
the initializations of the bound argument entities of the result,
as specified above, are all well-formed
.write_env is a sender adaptor
that accepts a sender and a queryable object, and
that returns a sender that,
when connected with a receiver
rcvr,
connects the adapted sender with a receiver
whose execution environment is the result of
joining the
queryable object
to the result of
get_env(rcvr). write_env is a customization point object
. For some subexpressions
sndr and
env,
if
decltype((sndr)) does not satisfy
sender or
if
decltype((env)) does not satisfy
queryable,
the expression
write_env(sndr, env) is ill-formed
. Otherwise, it is expression-equivalent to
make-sender(write_env, env, sndr).Let
write-env-t denote the type
decltype(auto(write_env)). The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
write-env-t as follows:
template<>
struct impls-for<write-env-t> : default-impls {
static constexpr auto join-env(const auto& state, const auto& env) noexcept {
return see below;
}
static constexpr auto get-env =
[](auto, const auto& state, const auto& rcvr) noexcept {
return join-env(state, FWD-ENV(get_env(rcvr)));
};
template<class Sndr, class... Env>
static consteval void check-types();
};
Invocation of
impls-for<write-env-t>::join-env
returns an object
e such that
- decltype(e) models queryable and
- given a query object q,
the expression e.query(q) is expression-equivalent
to state.query(q) if that expression is valid,
otherwise, e.query(q) is expression-equivalent
to env.query(q).
For a type
Sndr and a pack of types
Env,
let
State be
data-type<Sndr> and
let
JoinEnv be the pack
decltype(join-env(declval<State>(), FWD-ENV(declval<Env>()))). Then
impls-for<write-env-t>::check-types<Sndr, Env...>()
is expression-equivalent to
get_completion_signatures<child-
type<Sndr>, JoinEnv...>().unstoppable is a sender adaptor
that connects its inner sender
with a receiver that has the execution environment of the outer receiver
but with an object of type
never_stop_token
as the result of the
get_stop_token query. For a subexpression
sndr,
unstoppable(sndr) is expression-equivalent to
write_env(sndr, prop(get_stop_token, never_stop_token{})).starts_on adapts an input sender into a sender
that will start on an execution agent belonging to
a particular scheduler's associated execution resource
. The name
starts_on denotes a customization point object
. For subexpressions
sch and
sndr,
if
decltype((
sch)) does not satisfy
scheduler, or
decltype((sndr)) does not satisfy
sender,
starts_on(sch, sndr) is ill-formed
.Otherwise,
the expression
starts_on(sch, sndr) is expression-equivalent to:
transform_sender(
query-with-default(get_domain, sch, default_domain()),
make-sender(starts_on, sch, sndr))
except that
sch is evaluated only once
.Let
out_sndr and
env be subexpressions
such that
OutSndr is
decltype((out_sndr)). If
sender-for<OutSndr, starts_on_t> is
false,
then the expressions
starts_on.transform_env(out_sndr, env) and
starts_on.transform_sender(out_sndr, env) are ill-formed; otherwise
- starts_on.transform_env(out_sndr, env) is equivalent to:
auto&& [_, sch, _] = out_sndr;
return JOIN-ENV(SCHED-ENV(sch), FWD-ENV(env));
- starts_on.transform_sender(out_sndr, env) is equivalent to:
auto&& [_, sch, sndr] = out_sndr;
return let_value(
schedule(sch),
[sndr = std::forward_like<OutSndr>(sndr)]() mutable
noexcept(is_nothrow_move_constructible_v<decay_t<OutSndr>>) {
return std::move(sndr);
});
Let
out_sndr be a subexpression denoting
a sender returned from
starts_on(sch, sndr) or one equal to such, and
let
OutSndr be the type
decltype((out_sndr)). Let
out_rcvr be a subexpression denoting a receiver
that has an environment of type
Env
such that
sender_in<OutSndr, Env> is
true. Let
op be an lvalue referring to the operation state
that results from connecting
out_sndr with
out_rcvr. Calling
start(op) shall start
sndr
on an execution agent of the associated execution resource of
sch. If scheduling onto
sch fails,
an error completion on
out_rcvr shall be executed
on an unspecified execution agent
.continues_on adapts a sender into one
that completes on the specified scheduler
. The name
continues_on denotes a pipeable sender adaptor object
. For subexpressions
sch and
sndr,
if
decltype((sch)) does not satisfy
scheduler, or
decltype((sndr)) does not satisfy
sender,
continues_on(sndr, sch) is ill-formed
.Otherwise,
the expression
continues_on(sndr, sch) is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(continues_on, sch, sndr))
except that
sndr is evaluated only once
.The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
continues_on_t as follows:
namespace std::execution {
template<>
struct impls-for<continues_on_t> : default-impls {
static constexpr auto get-attrs =
[](const auto& data, const auto& child) noexcept -> decltype(auto) {
return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child)));
};
};
}
Let
sndr and
env be subexpressions
such that
Sndr is
decltype((sndr)). If
sender-for<Sndr, continues_on_t> is
false,
then
the expression
continues_on.transform_sender(sndr, env) is ill-formed;
otherwise, it is equal to:
auto [_, data, child] = sndr;
return schedule_from(std::move(data), std::move(child));
[
Note 1:
This causes the
continues_on(sndr, sch) sender to become
schedule_from(sch, sndr) when it is connected with a receiver
whose execution domain does not customize
continues_on. —
end note]
Let
out_sndr be a subexpression denoting
a sender returned from
continues_on(sndr, sch) or one equal to such, and
let
OutSndr be the type
decltype((out_sndr)). Let
out_rcvr be a subexpression denoting a receiver
that has an environment of type
Env
such that
sender_in<OutSndr, Env> is
true. Let
op be an lvalue referring to the operation state
that results from connecting
out_sndr with
out_rcvr. Calling
start(op) shall
start
sndr on the current execution agent and
execute completion operations on
out_rcvr
on an execution agent of the execution resource associated with
sch. If scheduling onto
sch fails,
an error completion on
out_rcvr shall be executed
on an unspecified execution agent
.schedule_from schedules work dependent on the completion of a sender
onto a scheduler's associated execution resource
. [
Note 1:
schedule_from is not meant to be used in user code;
it is used in the implementation of
continues_on. —
end note]
The name
schedule_from denotes a customization point object
. For some subexpressions
sch and
sndr,
let
Sch be
decltype((sch)) and
Sndr be
decltype((sndr)). If
Sch does not satisfy
scheduler, or
Sndr does not satisfy
sender,
schedule_from(sch, sndr) is ill-formed
.Otherwise,
the expression
schedule_from(sch, sndr) is expression-equivalent to:
transform_sender(
query-with-default(get_domain, sch, default_domain()),
make-sender(schedule_from, sch, sndr))
except that
sch is evaluated only once
.The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
schedule_from_t as follows:
namespace std::execution {
template<>
struct impls-for<schedule_from_t> : default-impls {
static constexpr auto get-attrs = see below;
static constexpr auto get-state = see below;
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
}
The member impls-for<schedule_from_t>::get-attrs
is initialized with a callable object equivalent to the following lambda:
[](const auto& data, const auto& child) noexcept -> decltype(auto) {
return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child)));
}
The member
impls-for<schedule_from_t>::get-state
is initialized with a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(see below)
requires sender_in<child-type<Sndr>, FWD-ENV-T(env_of_t<Rcvr>)> {
auto& [_, sch, child] = sndr;
using sched_t = decltype(auto(sch));
using variant_t = see below;
using receiver_t = see below;
using operation_t = connect_result_t<schedule_result_t<sched_t>, receiver_t>;
constexpr bool nothrow = noexcept(connect(schedule(sch), receiver_t{nullptr}));
struct state-type {
Rcvr& rcvr;
variant_t async-result;
operation_t op-state;
explicit state-type(sched_t sch, Rcvr& rcvr) noexcept(nothrow)
: rcvr(rcvr), op-state(connect(schedule(sch), receiver_t{this})) {}
};
return state-type{sch, rcvr};
}
template<class Sndr, class... Env>
static consteval void check-types();
Effects: Equivalent to:
get_completion_signatures<schedule_result_t<data-type<Sndr>>, FWD-ENV-T(Env)...>();
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
decay-copyable-result-datums(cs);
Objects of the local class
state-type can be used
to initialize a structured binding
.Let
Sigs be
a pack of the arguments to the
completion_signatures specialization
named by
completion_signatures_of_t<child-type<Sndr>, FWD-ENV-T(env_of_t<Rcvr>)>. Let
as-tuple be an alias template such that
as-tuple<Tag(Args...)> denotes
the type
decayed-tuple<Tag, Args...>, and
let
is-nothrow-decay-copy-sig be a variable template such that
auto(is-nothrow-decay-copy-sig<Tag(Args...
)>) is
a constant expression of type
bool and
equal to
(is_nothrow_constructible_v<decay_t<Args>, Args> && ...). Let
error-completion be a pack consisting of
the type
set_error_t(exception_ptr)
if
(is-nothrow-decay-copy-sig<Sigs> &&...) is
false,
and an empty pack otherwise
. Then
variant_t denotes
the type
variant<monostate, as-tuple<Sigs>..., error-completion...>,
except with duplicate types removed
.receiver_t is an alias for the following exposition-only class:
namespace std::execution {
struct receiver-type {
using receiver_concept = receiver_t;
state-type* state;
void set_value() && noexcept {
visit(
[this]<class Tuple>(Tuple& result) noexcept -> void {
if constexpr (!same_as<monostate, Tuple>) {
auto& [tag, ...args] = result;
tag(std::move(state->rcvr), std::move(args)...);
}
},
state->async-result);
}
template<class Error>
void set_error(Error&& err) && noexcept {
execution::set_error(std::move(state->rcvr), std::forward<Error>(err));
}
void set_stopped() && noexcept {
execution::set_stopped(std::move(state->rcvr));
}
decltype(auto) get_env() const noexcept {
return FWD-ENV(execution::get_env(state->rcvr));
}
};
}
The expression in the
noexcept clause of the lambda is
true
if the construction of the returned
state-type object
is not potentially throwing;
otherwise,
false.The member impls-for<schedule_from_t>::complete
is initialized with a callable object equivalent to the following lambda:
[]<class Tag, class... Args>(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept
-> void {
using result_t = decayed-tuple<Tag, Args...>;
constexpr bool nothrow = (is_nothrow_constructible_v<decay_t<Args>, Args> && ...);
try {
state.async-result.template emplace<result_t>(Tag(), std::forward<Args>(args)...);
} catch (...) {
if constexpr (!nothrow)
state.async-result.template emplace<tuple<set_error_t,
exception_ptr>>(set_error, current_exception());
}
start(state.op-state);
};
Let
out_sndr be a subexpression denoting
a sender returned from
schedule_from(sch, sndr) or one equal to such,
and let
OutSndr be the type
decltype((out_sndr)). Let
out_rcvr be a subexpression denoting a receiver
that has an environment of type
Env
such that
sender_in<OutSndr, Env> is
true. Let
op be an lvalue referring to the operation state
that results from connecting
out_sndr with
out_rcvr. Calling
start(op) shall
start
sndr on the current execution agent and
execute completion operations on
out_rcvr
on an execution agent of the execution resource associated with
sch. If scheduling onto
sch fails,
an error completion on
out_rcvr shall be executed
on an unspecified execution agent
.The
on sender adaptor has two forms:
on(sch, sndr),
which starts a sender
sndr on an execution agent
belonging to a scheduler
sch's associated execution resource and
that, upon
sndr's completion,
transfers execution back to the execution resource
on which the
on sender was started
. on(sndr, sch, closure),
which upon completion of a sender
sndr,
transfers execution to an execution agent
belonging to a scheduler
sch's associated execution resource,
then executes a sender adaptor closure
closure
with the async results of the sender, and
that then transfers execution back to the execution resource
on which
sndr completed
.
The name
on denotes a pipeable sender adaptor object
. For subexpressions
sch and
sndr,
on(sch, sndr) is ill-formed if any of the following is
true:
- decltype((sch)) does not satisfy scheduler, or
- decltype((sndr)) does not satisfy sender and
sndr is not
a pipeable sender adaptor closure object ([exec.adapt.obj]), or
- decltype((sndr)) satisfies sender and
sndr is also a pipeable sender adaptor closure object.
Otherwise, if
decltype((sndr)) satisfies
sender,
the expression
on(sch, sndr) is expression-equivalent to:
transform_sender(
query-with-default(get_domain, sch, default_domain()),
make-sender(on, sch, sndr))
except that
sch is evaluated only once
.For subexpressions
sndr,
sch, and
closure, if
- decltype((sch)) does not satisfy scheduler, or
- decltype((sndr)) does not satisfy sender, or
- closure is not a pipeable sender adaptor closure object ([exec.adapt.obj]),
the expression
on(sndr, sch, closure) is ill-formed;
otherwise, it is expression-equivalent to:
transform_sender(
get-domain-early(sndr),
make-sender(on, product-type{sch, closure}, sndr))
except that
sndr is evaluated only once
.Let
out_sndr and
env be subexpressions,
let
OutSndr be
decltype((out_sndr)), and
let
Env be
decltype((env)). If
sender-for<OutSndr, on_t> is
false,
then the expressions
on.transform_env(out_sndr, env) and
on.transform_sender(out_sndr, env) are ill-formed
.Otherwise:
Let
not-a-scheduler be an unspecified empty class type
.The expression
on.transform_env(out_sndr, env)
has effects equivalent to:
auto&& [_, data, _] = out_sndr;
if constexpr (scheduler<decltype(data)>) {
return JOIN-ENV(SCHED-ENV(std::forward_like<OutSndr>(data)), FWD-ENV(std::forward<Env>(env)));
} else {
return std::forward<Env>(env);
}
The expression
on.transform_sender(out_sndr, env)
has effects equivalent to:
auto&& [_, data, child] = out_sndr;
if constexpr (scheduler<decltype(data)>) {
auto orig_sch =
query-with-default(get_scheduler, env, not-a-scheduler());
if constexpr (same_as<decltype(orig_sch), not-a-scheduler>) {
return not-a-sender{};
} else {
return continues_on(
starts_on(std::forward_like<OutSndr>(data), std::forward_like<OutSndr>(child)),
std::move(orig_sch));
}
} else {
auto& [sch, closure] = data;
auto orig_sch = query-with-default(
get_completion_scheduler<set_value_t>,
get_env(child),
query-with-default(get_scheduler, env, not-a-scheduler()));
if constexpr (same_as<decltype(orig_sch), not-a-scheduler>) {
return not-a-sender{};
} else {
return write_env(
continues_on(
std::forward_like<OutSndr>(closure)(
continues_on(
write_env(std::forward_like<OutSndr>(child), SCHED-ENV(orig_sch)),
sch)),
orig_sch),
SCHED-ENV(sch));
}
}
Let
out_sndr be a subexpression denoting
a sender returned from
on(sch, sndr) or one equal to such, and
let
OutSndr be the type
decltype((out_sndr)). Let
out_rcvr be a subexpression denoting a receiver
that has an environment of type
Env
such that
sender_in<OutSndr, Env> is
true. Let
op be an lvalue referring to the operation state
that results from connecting
out_sndr with
out_rcvr. Calling
start(op) shall
- remember the current scheduler, get_scheduler(get_env(rcvr));
- start sndr on an execution agent belonging to
sch's associated execution resource;
- upon sndr's completion,
transfer execution back to the execution resource
associated with the scheduler remembered in step 1; and
- forward sndr's async result to out_rcvr.
If any scheduling operation fails,
an error completion on
out_rcvr shall be executed
on an unspecified execution agent
.Let
out_sndr be a subexpression denoting
a sender returned from
on(sndr, sch, closure) or one equal to such, and
let
OutSndr be the type
decltype((out_sndr)). Let
out_rcvr be a subexpression denoting a receiver
that has an environment of type
Env
such that
sender_in<OutSndr, Env> is
true. Let
op be an lvalue referring to the operation state
that results from connecting
out_sndr with
out_rcvr. Calling
start(op) shall
- remember the current scheduler,
which is the first of the following expressions that is well-formed:
- get_completion_scheduler<set_value_t>(get_env(sndr))
- get_scheduler(get_env(rcvr));
- start sndr on the current execution agent;
- upon sndr's completion,
transfer execution to an agent
owned by sch's associated execution resource;
- forward sndr's async result as if by
connecting and starting a sender closure(S),
where S is a sender
that completes synchronously with sndr's async result; and
- upon completion of the operation started in the previous step,
transfer execution back to the execution resource
associated with the scheduler remembered in step 1 and
forward the operation's async result to out_rcvr.
If any scheduling operation fails,
an error completion on
out_rcvr shall be executed on
an unspecified execution agent
.then attaches an invocable as a continuation
for an input sender's value completion operation
. upon_error and
upon_stopped do the same
for the error and stopped completion operations, respectively,
sending the result of the invocable as a value completion
. The names
then,
upon_error, and
upon_stopped
denote pipeable sender adaptor objects
. Let the expression
then-cpo be one of
then,
upon_error, or
upon_stopped. For subexpressions
sndr and
f,
if
decltype((sndr)) does not satisfy
sender, or
decltype((f)) does not satisfy
movable-value,
then-cpo(sndr, f) is ill-formed
.Otherwise,
the expression
then-cpo(sndr, f) is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(then-cpo, f, sndr))
except that
sndr is evaluated only once
.For
then,
upon_error, and
upon_stopped,
let
set-cpo be
set_value,
set_error, and
set_stopped, respectively
. The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
then-cpo as follows:
namespace std::execution {
template<>
struct impls-for<decayed-typeof<then-cpo>> : default-impls {
static constexpr auto complete =
[]<class Tag, class... Args>
(auto, auto& fn, auto& rcvr, Tag, Args&&... args) noexcept -> void {
if constexpr (same_as<Tag, decayed-typeof<set-cpo>>) {
TRY-SET-VALUE(rcvr,
invoke(std::move(fn), std::forward<Args>(args)...));
} else {
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}
};
template<class Sndr, class... Env>
static consteval void check-types();
};
}
template<class Sndr, class... Env>
static consteval void check-types();
Effects: Equivalent to:
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
auto fn = []<class... Ts>(decayed-typeof<set-cpo>(*)(Ts...)) {
if constexpr (!invocable<remove_cvref_t<data-type<Sndr>>, Ts...>)
throw unspecified-exception();
};
cs.for-each(overload-set{fn, [](auto){}});
The expression
then-cpo(sndr, f) has undefined behavior
unless it returns a sender
out_sndr that
- invokes f or a copy of such
with the value, error, or stopped result datums of sndr
for then, upon_error, and upon_stopped, respectively,
using the result value of f as out_sndr's value completion, and
- forwards all other completion operations unchanged.
let_value,
let_error, and
let_stopped transform
a sender's value, error, and stopped completions, respectively,
into a new child asynchronous operation
by passing the sender's result datums to a user-specified callable,
which returns a new sender that is connected and started
. For
let_value,
let_error, and
let_stopped,
let
set-cpo be
set_value,
set_error, and
set_stopped, respectively
. Let the expression
let-cpo be one of
let_value,
let_error, or
let_stopped. For a subexpression
sndr,
let
let-env(sndr) be expression-equivalent to
the first well-formed expression below:
- SCHED-ENV(get_completion_scheduler<decayed-typeof<set-cpo>>(get_env(sndr)))
- MAKE-ENV(get_domain, get_domain(get_env(sndr)))
- (void(sndr), env<>{})
The names
let_value,
let_error, and
let_stopped denote
pipeable sender adaptor objects
. For subexpressions
sndr and
f,
let
F be the decayed type of
f. If
decltype((sndr)) does not satisfy
sender or
if
decltype((f)) does not satisfy
movable-value,
the expression
let-cpo(sndr, f) is ill-formed
. If
F does not satisfy
invocable,
the expression
let_stopped(sndr, f) is ill-formed
.Otherwise,
the expression
let-cpo(sndr, f) is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(let-cpo, f, sndr))
except that
sndr is evaluated only once
.The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
let-cpo as follows:
namespace std::execution {
template<class State, class Rcvr, class... Args>
void let-bind(State& state, Rcvr& rcvr, Args&&... args);
template<>
struct impls-for<decayed-typeof<let-cpo>> : default-impls {
static constexpr auto get-state = see below;
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
}
Let receiver2 denote the following exposition-only class template:
namespace std::execution {
template<class Rcvr, class Env>
struct receiver2 {
using receiver_concept = receiver_t;
template<class... Args>
void set_value(Args&&... args) && noexcept {
execution::set_value(std::move(rcvr), std::forward<Args>(args)...);
}
template<class Error>
void set_error(Error&& err) && noexcept {
execution::set_error(std::move(rcvr), std::forward<Error>(err));
}
void set_stopped() && noexcept {
execution::set_stopped(std::move(rcvr));
}
decltype(auto) get_env() const noexcept {
return see below;
}
Rcvr& rcvr;
Env env;
};
}
Invocation of the function
receiver2::get_env
returns an object
e such that
- decltype(e) models queryable and
- given a query object q,
the expression e.query(q) is expression-equivalent
to env.query(q) if that expression is valid;
otherwise,
if the type of q satisfies forwarding-query,
e.query(q) is expression-equivalent
to get_env(rcvr).query(q);
otherwise,
e.query(q) is ill-formed.
template<class Sndr, class... Env>
static consteval void check-types();
Effects: Equivalent to:
using LetFn = remove_cvref_t<data-type<Sndr>>;
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
auto fn = []<class... Ts>(decayed-typeof<set-cpo>(*)(Ts...)) {
if constexpr (!is-valid-let-sender)
throw unspecified-exception();
};
cs.for-each(overload-set(fn, [](auto){}));
where
is-valid-let-sender is
true if and only if
all of the following are
true:
where
env-t is the pack
decltype(let-cpo.transform_env(declval<Sndr>(), declval<Env>())). impls-for<decayed-typeof<let-cpo>>::get-state
is initialized with a callable object equivalent to the following:
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below {
auto& [_, fn, child] = sndr;
using fn_t = decay_t<decltype(fn)>;
using env_t = decltype(let-env(child));
using args_variant_t = see below;
using ops2_variant_t = see below;
struct state-type {
fn_t fn;
env_t env;
args_variant_t args;
ops2_variant_t ops2;
};
return state-type{allocator-aware-forward(std::forward_like<Sndr>(fn), rcvr),
let-env(child), {}, {}};
}
Let
Sigs be a pack of the arguments
to the
completion_signatures specialization named by
completion_signatures_of_t<child-type<Sndr>, FWD-ENV-T(env_of_t<Rcvr>)>. Let
LetSigs be a pack of those types in
Sigs
with a return type of
decayed-typeof<set-cpo>. Let
as-tuple be an alias template
such that
as-tuple<Tag(Args...)> denotes
the type
decayed-tuple<Args...>. Then
args_variant_t denotes
the type
variant<monostate, as-tuple<LetSigs>...>
except with duplicate types removed
.Given a type
Tag and a pack
Args,
let
as-sndr2 be an alias template
such that
as-sndr2<Tag(Args...)> denotes
the type
call-result-t<F, decay_t<Args>&...>. Then
ops2_variant_t denotes
the type
variant<monostate, connect_result_t<as-sndr2<LetSigs>, receiver2<Rcvr, env_t>>...>
except with duplicate types removed
.The
requires-clause constraining the above lambda is satisfied
if and only if
the types
args_variant_t and
ops2_variant_t are well-formed
.The exposition-only function template let-bind
has effects equivalent to:
using args_t = decayed-tuple<Args...>;
auto mkop2 = [&] {
return connect(
apply(std::move(state.fn),
state.args.template emplace<args_t>(std::forward<Args>(args)...)),
receiver2{rcvr, std::move(state.env)});
};
start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));
impls-for<decayed-typeof<let-cpo>>::complete
is initialized with a callable object equivalent to the following:
[]<class Tag, class... Args>
(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void {
if constexpr (same_as<Tag, decayed-typeof<set-cpo>>) {
TRY-EVAL(rcvr, let-bind(state, rcvr, std::forward<Args>(args)...));
} else {
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}
}
Let
sndr and
env be subexpressions, and
let
Sndr be
decltype((sndr)). If
sender-for<Sndr, decayed-typeof<let-cpo>>
is
false,
then the expression
let-cpo.transform_env(sndr, env)
is ill-formed
. Otherwise, it is equal to:
auto& [_, _, child] = sndr;
return JOIN-ENV(let-env(child), FWD-ENV(env));
Let the subexpression
out_sndr denote
the result of the invocation
let-cpo(sndr, f) or
an object equal to such, and
let the subexpression
rcvr denote a receiver
such that the expression
connect(out_sndr, rcvr) is well-formed
. The expression
connect(out_sndr, rcvr) has undefined behavior
unless it creates an asynchronous operation (
[exec.async.ops]) that,
when started:
- invokes f when set-cpo is called
with sndr's result datums,
- makes its completion dependent on
the completion of a sender returned by f, and
- propagates the other completion operations sent by sndr.
bulk,
bulk_chunked, and
bulk_unchunked
run a task repeatedly for every index in an index space
. The names
bulk,
bulk_chunked, and
bulk_unchunked
denote pipeable sender adaptor objects
. Let
bulk-algo be either
bulk,
bulk_chunked, or
bulk_unchunked. For subexpressions
sndr,
policy,
shape, and
f,
let
Policy be
remove_cvref_t<decltype(policy)>,
Shape be
decltype(auto(shape)), and
Func be
decay_t<decltype((f))>. If
- decltype((sndr)) does not satisfy sender, or
- is_execution_policy_v<Policy> is false, or
- Shape does not satisfy integral, or
- Func does not model copy_constructible,
bulk-algo(sndr, policy, shape, f) is ill-formed
.Otherwise,
the expression bulk-algo(sndr, policy, shape, f)
is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(
bulk-algo, product-type<see below, Shape, Func>{policy, shape, f}, sndr))
except that
sndr is evaluated only once
. The first template argument of
product-type is
Policy
if
Policy models
copy_constructible, and
const Policy& otherwise
.Let
sndr and
env be subexpressions such that
Sndr is
decltype((sndr)). If
sender-for<Sndr, bulk_t> is
false, then
the expression
bulk.transform_sender(sndr, env) is ill-formed;
otherwise, it is equivalent to:
auto [_, data, child] = sndr;
auto& [policy, shape, f] = data;
auto new_f = [func = std::move(f)](Shape begin, Shape end, auto&&... vs)
noexcept(noexcept(f(begin, vs...))) {
while (begin != end) func(begin++, vs...);
}
return bulk_chunked(std::move(child), policy, shape, std::move(new_f));
[
Note 1:
This causes the
bulk(sndr, policy, shape, f) sender to be
expressed in terms of
bulk_chunked(sndr, policy, shape, f) when
it is connected to a receiver whose
execution domain does not customize
bulk. —
end note]
The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
bulk_chunked_t as follows:
namespace std::execution {
template<>
struct impls-for<bulk_chunked_t> : default-impls {
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
}
The member
impls-for<bulk_chunked_t>::complete
is initialized with a callable object equivalent to the following lambda:
[]<class Index, class State, class Rcvr, class Tag, class... Args>
(Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept
-> void requires see below {
if constexpr (same_as<Tag, set_value_t>) {
auto& [policy, shape, f] = state;
constexpr bool nothrow = noexcept(f(auto(shape), auto(shape), args...));
TRY-EVAL(rcvr, [&]() noexcept(nothrow) {
f(static_cast<decltype(auto(shape))>(0), auto(shape), args...);
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}());
} else {
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}
}
The expression in the
requires-clause of the lambda above is
true if and only
if
Tag denotes a type other than
set_value_t or
if the expression
f(auto(shape), auto(shape), args...) is well-formed
.template<class Sndr, class... Env>
static consteval void check-types();
Effects: Equivalent to:
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
auto fn = []<class... Ts>(set_value_t(*)(Ts...)) {
using data_type = data-type<Sndr>;
if constexpr (!invocable<remove_cvref_t<child-type<data_type>&,
remove_cvref_t<data-type<data_type>>, Ts&...>)
throw unspecified-exception();
};
cs.for-each(overload-set(fn, [](auto){}));
The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
bulk_unchunked_t as follows:
namespace std::execution {
template<>
struct impls-for<bulk_unchunked_t> : default-impls {
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
}
The member
impls-for<bulk_unchunked_t>::complete
is initialized with a callable object equivalent to the following lambda:
[]<class Index, class State, class Rcvr, class Tag, class... Args>
(Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept
-> void requires see below {
if constexpr (same_as<Tag, set_value_t>) {
auto& [policy, shape, f] = state;
constexpr bool nothrow = noexcept(f(auto(shape), args...));
TRY-EVAL(rcvr, [&]() noexcept(nothrow) {
for (decltype(auto(shape)) i = 0; i < shape; ++i) {
f(auto(i), args...);
}
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}());
} else {
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}
}
The expression in the
requires-clause of the lambda above
is
true if and only
if
Tag denotes a type other than
set_value_t or
if the expression
f(auto(shape), args...) is well-formed
.template<class Sndr, class... Env>
static consteval void check-types();
Effects: Equivalent to:
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
auto fn = []<class... Ts>(set_value_t(*)(Ts...)) {
using data_type = data-type<Sndr>;
if constexpr (!invocable<remove_cvref_t<child-type<data_type>&,
remove_cvref_t<data-type<data_type>>, Ts&...>)
throw unspecified-exception();
};
cs.for-each(overload-set(fn, [](auto){}));
Let the subexpression
out_sndr denote
the result of the invocation
bulk-algo(sndr, policy, shape, f) or
an object equal to such, and
let the subexpression
rcvr denote a receiver
such that the expression
connect(out_sndr, rcvr) is well-formed
. The expression
connect(out_sndr, rcvr) has undefined behavior
unless it creates an asynchronous operation (
[exec.async.ops]) that,
when started:
If
sndr has a successful completion, where
args is a pack of lvalue subexpressions
referring to the value completion result datums of
sndr, or
decayed copies of those values if they model
copy_constructible,
then:
If out_sndr also completes successfully, then:
for bulk,
invokes f(i, args...) for every i of type Shape
from 0 to shape;
for bulk_unchunked,
invokes f(i, args...) for every i of type Shape
from 0 to shape;
Recommended practice: The underlying scheduler should execute each iteration
on a distinct execution agent
. for
bulk_chunked,
invokes
f(b, e, args...) zero or more times
with pairs of
b and
e of type
Shape
in range [
0, shape],
such that
b<e and
for every
i of type
Shape from
0 to
shape,
there is exactly one invocation with a pair
b and
e,
such that
i is in the range [
b, e)
.
If
out_sndr completes with
set_error(rcvr, eptr), then
the asynchronous operation may invoke a subset of
the invocations of
f
before the error completion handler is called, and
eptr is an
exception_ptr containing either:
- an exception thrown by an invocation of f, or
- a bad_alloc exception if
the implementation fails to allocate required resources, or
- an exception derived from runtime_error.
If
out_sndr completes with
set_stopped(rcvr), then
the asynchronous operation may invoke a subset of
the invocations of
f
before the stopped completion handler
.
If
sndr does not complete with
set_value, then
the completion is forwarded to
recv.For
bulk-algo,
the parameter
policy describes
the manner in which
the execution of the asynchronous operations corresponding to these algorithms
may be parallelized and
the manner in which
they apply
f.
[
Note 2:
The asynchronous operation corresponding to
bulk-algo(sndr, policy, shape, f)
can complete with
set_stopped if cancellation is requested or
ignore cancellation requests
. —
end note]
when_all and
when_all_with_variant
both adapt multiple input senders into a sender
that completes when all input senders have completed
. when_all only accepts senders
with a single value completion signature and
on success concatenates all the input senders' value result datums
into its own value completion operation
. when_all_with_variant(sndrs...) is semantically equivalent to
w
hen_all(into_variant(sndrs)...),
where
sndrs is a pack of subexpressions
whose types model
sender. The names
when_all and
when_all_with_variant denote
customization point objects
. Let
sndrs be a pack of subexpressions,
let
Sndrs be a pack of the types
decltype((sndrs))..., and
let
CD be
the type
common_type_t<decltype(get-domain-early(sndrs))...>. Let
CD2 be
CD if
CD is well-formed, and
default_domain otherwise
. The expressions
when_all(sndrs...) and
when_all_with_variant(sndrs...) are ill-formed
if any of the following is
true:
- sizeof...(sndrs) is 0, or
- (sender<Sndrs> && ...) is false.
The expression when_all(sndrs...) is expression-equivalent to:
transform_sender(CD2(), make-sender(when_all, {}, sndrs...))
The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
when_all_t as follows:
namespace std::execution {
template<>
struct impls-for<when_all_t> : default-impls {
static constexpr auto get-attrs = see below;
static constexpr auto get-env = see below;
static constexpr auto get-state = see below;
static constexpr auto start = see below;
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
}
Let
make-when-all-env be
the following exposition-only function template:
template<class Env>
constexpr auto make-when-all-env(inplace_stop_source& stop_src,
Env&& env) noexcept;
Returns: An object
e such that
- decltype(e) models queryable, and
- e.query(get_stop_token) is expression-equivalent to
stop_src.get_token(), and
- given a query object q
with type other than cv get_stop_token_t,
e.query(q) is expression-equivalent to env.query(q)
if the type of q satisfies forwarding-query, and
ill-formed otherwise.
Let
when-all-env be an alias template such that
when-all-env<Env> denotes the type
decltype(make-
when-all-env(declval<inplace_stop_source&>(), declval<Env>())).template<class Sndr, class... Env>
static consteval void check-types();
Let
Is be the pack of integral template arguments of
the
integer_sequence specialization denoted by
indices-for<Sndr>.Effects: Equivalent to:
auto fn = []<class Child>() {
auto cs = get_completion_signatures<Child, when-all-env<Env>...>();
if constexpr (cs.count-of(set_value) >= 2)
throw unspecified-exception();
decay-copyable-result-datums(cs);
};
(fn.template operator()<child-type<Sndr, Is>>(), ...);
Throws: Any exception thrown as a result of evaluating the
Effects, or
an exception of type
unspecified-exception (
[exec.snd.general])
when
CD is ill-formed
. The member
impls-for<when_all_t>::get-attrs
is initialized with a callable object
equivalent to the following lambda expression:
[](auto&&, auto&&... child) noexcept {
if constexpr (same_as<CD, default_domain>) {
return env<>();
} else {
return MAKE-ENV(get_domain, CD());
}
}
The member impls-for<when_all_t>::get-env
is initialized with a callable object
equivalent to the following lambda expression:
[]<class State, class Rcvr>(auto&&, State& state, const Receiver& rcvr) noexcept {
return make-when-all-env(state.stop-src, get_env(rcvr));
}
The member impls-for<when_all_t>::get-state
is initialized with a callable object
equivalent to the following lambda expression:
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(noexcept(e)) -> decltype(e) {
return e;
}
where e is the expression
std::forward<Sndr>(sndr).apply(make-state<Rcvr>())
and where make-state is the following exposition-only class template:
enum class disposition { started, error, stopped };
template<class Rcvr>
struct make-state {
template<class... Sndrs>
auto operator()(auto, auto, Sndrs&&... sndrs) const {
using values_tuple = see below;
using errors_variant = see below;
using stop_callback = stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>;
struct state-type {
void arrive(Rcvr& rcvr) noexcept {
if (0 == --count) {
complete(rcvr);
}
}
void complete(Rcvr& rcvr) noexcept;
atomic<size_t> count{sizeof...(sndrs)};
inplace_stop_source stop_src{};
atomic<disposition> disp{disposition::started};
errors_variant errors{};
values_tuple values{};
optional<stop_callback> on_stop{nullopt};
};
return state-type{};
}
};
Let
copy-fail be
exception_ptr
if decay-copying any of the child senders' result datums can potentially throw;
otherwise,
none-such,
where
none-such is an unspecified empty class type
.The alias
values_tuple denotes the type
tuple<value_types_of_t<Sndrs, FWD-ENV-T(env_of_t<Rcvr>), decayed-tuple, optional>...>
if that type is well-formed; otherwise,
tuple<>.The alias
errors_variant denotes
the type
variant<none-such, copy-fail, Es...>
with duplicate types removed,
where
Es is the pack of the decayed types
of all the child senders' possible error result datums
.The member
void state-type::complete(Rcvr& rcvr) noexcept
behaves as follows:
- If disp is equal to disposition::started,
evaluates:
auto tie = []<class... T>(tuple<T...>& t) noexcept { return tuple<T&...>(t); };
auto set = [&](auto&... t) noexcept { set_value(std::move(rcvr), std::move(t)...); };
on_stop.reset();
apply(
[&](auto&... opts) noexcept {
apply(set, tuple_cat(tie(*opts)...));
},
values);
- Otherwise,
if disp is equal to disposition::error,
evaluates:
on_stop.reset();
visit(
[&]<class Error>(Error& error) noexcept {
if constexpr (!same_as<Error, none-such>) {
set_error(std::move(rcvr), std::move(error));
}
},
errors);
- Otherwise, evaluates:
if constexpr (sends-stopped) {
on_stop.reset();
set_stopped(std::move(rcvr));
}
where sends-stopped equals true
if and only if there exists an element S of Sndrs such that
completion_signatures_of_t<S, when-all-env<Env>>
contains set_stopped_t().
The member impls-for<when_all_t>::start
is initialized with a callable object
equivalent to the following lambda expression:
[]<class State, class Rcvr, class... Ops>(
State& state, Rcvr& rcvr, Ops&... ops) noexcept -> void {
state.on_stop.emplace(
get_stop_token(get_env(rcvr)),
on-stop-request{state.stop_src});
(start(ops), ...);
}
The member
impls-for<when_all_t>::complete
is initialized with a callable object
equivalent to the following lambda expression:
[]<class Index, class State, class Rcvr, class Set, class... Args>(
this auto& complete, Index, State& state, Rcvr& rcvr, Set, Args&&... args) noexcept -> void {
if constexpr (same_as<Set, set_error_t>) {
if (disposition::error != state.disp.exchange(disposition::error)) {
state.stop_src.request_stop();
TRY-EMPLACE-ERROR(state.errors, std::forward<Args>(args)...);
}
} else if constexpr (same_as<Set, set_stopped_t>) {
auto expected = disposition::started;
if (state.disp.compare_exchange_strong(expected, disposition::stopped)) {
state.stop_src.request_stop();
}
} else if constexpr (!same_as<decltype(State::values), tuple<>>) {
if (state.disp == disposition::started) {
auto& opt = get<Index::value>(state.values);
TRY-EMPLACE-VALUE(complete, opt, std::forward<Args>(args)...);
}
}
state.arrive(rcvr);
}
where
TRY-EMPLACE-ERROR(v, e),
for subexpressions
v and
e, is equivalent to:
try {
v.template emplace<decltype(auto(e))>(e);
} catch (...) {
v.template emplace<exception_ptr>(current_exception());
}
if the expression
decltype(auto(e))(e) is potentially throwing;
otherwise,
v.template emplace<decltype(auto(e))>(e);
and where
TRY-EMPLACE-VALUE(c, o, as...),
for subexpressions
c,
o, and pack of subexpressions
as,
is equivalent to:
try {
o.emplace(as...);
} catch (...) {
c(Index(), state, rcvr, set_error, current_exception());
return;
}
if the expression
decayed-tuple<decltype(as)...>{as...}
is potentially throwing;
otherwise,
o.emplace(as...).The expression when_all_with_variant(sndrs...)
is expression-equivalent to:
transform_sender(CD2(), make-sender(when_all_with_variant, {}, sndrs...));
Given subexpressions
sndr and
env,
if
sender-for<decltype((sndr)), when_all_with_variant_t>
is
false,
then the expression
when_all_with_variant.transform_sender(sndr, env)
is ill-formed;
otherwise, it is equivalent to:
auto&& [_, _, ...child] = sndr;
return when_all(into_variant(std::forward_like<decltype((sndr))>(child))...);
[
Note 1:
This causes the
when_all_with_variant(sndrs...) sender
to become
when_all(into_variant(sndrs)...)
when it is connected with a receiver
whose execution domain does not customize
when_all_with_variant. —
end note]
into_variant adapts a sender with multiple value completion signatures
into a sender with just one value completion signature
consisting of a
variant of
tuples
. The name
into_variant denotes a pipeable sender adaptor object
. For a subexpression
sndr, let
Sndr be
decltype((sndr)). If
Sndr does not satisfy
sender,
into_variant(sndr) is ill-formed
.Otherwise, the expression
into_variant(sndr)
is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(into_variant, {}, sndr))
except that
sndr is only evaluated once
.The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
into_variant as follows:
namespace std::execution {
template<>
struct impls-for<into_variant_t> : default-impls {
static constexpr auto get-state = see below;
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types() {
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
decay-copyable-result-datums(cs);
}
};
}
The member impls-for<into_variant_t>::get-state
is initialized with a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept
-> type_identity<value_types_of_t<child-type<Sndr>, FWD-ENV-T(env_of_t<Rcvr>)>> {
return {};
}
The member
impls-for<into_variant_t>::complete
is initialized with a callable object equivalent to the following lambda:
[]<class State, class Rcvr, class Tag, class... Args>(
auto, State, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void {
if constexpr (same_as<Tag, set_value_t>) {
using variant_type = State::type;
TRY-SET-VALUE(rcvr, variant_type(decayed-tuple<Args...>{std::forward<Args>(args)...}));
} else {
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}
}
stopped_as_optional maps a sender's stopped completion operation
into a value completion operation as a disengaged
optional. The sender's value completion operation
is also converted into an
optional. The result is a sender that never completes with stopped,
reporting cancellation by completing with a disengaged
optional.The name
stopped_as_optional denotes a pipeable sender adaptor object
. For a subexpression
sndr, let
Sndr be
decltype((sndr)). The expression
stopped_as_optional(sndr) is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(stopped_as_optional, {}, sndr))
except that
sndr is only evaluated once
.The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
stopped_as_optional_t as follows:
namespace std::execution {
template<>
struct impls-for<stopped_as_optional_t> : default-impls {
template<class Sndr, class... Env>
static consteval void check-types() {
default-impls::check-types<Sndr, Env...>();
if constexpr (!requires {
requires (!same_as<void, single-sender-value-type<child-type<Sndr>,
FWD-ENV-T(Env)...>>); })
throw unspecified-exception();
}
};
}
Let
sndr and
env be subexpressions
such that
Sndr is
decltype((sndr)) and
Env is
decltype((env)). If
sender-for<Sndr, stopped_as_optional_t>
is
false
then the expression
stopped_as_optional.transform_sender(sndr, env)
is ill-formed;
otherwise,
if
sender_in<child-type<Sndr>, FWD-ENV-T(Env)>
is
false,
the expression
stopped_as_optional.transform_sender(sndr, env)
is equivalent to
not-a-sender();
otherwise, it is equivalent to:
auto&& [_, _, child] = sndr;
using V = single-sender-value-type<child-type<Sndr>, FWD-ENV-T(Env)>;
return let_stopped(
then(std::forward_like<Sndr>(child),
[]<class... Ts>(Ts&&... ts) noexcept(is_nothrow_constructible_v<V, Ts...>) {
return optional<V>(in_place, std::forward<Ts>(ts)...);
}),
[]() noexcept { return just(optional<V>()); });
stopped_as_error maps an input sender's stopped completion operation
into an error completion operation as a custom error type
. The result is a sender that never completes with stopped,
reporting cancellation by completing with an error
.The name
stopped_as_error denotes a pipeable sender adaptor object
. For some subexpressions
sndr and
err,
let
Sndr be
decltype((sndr)) and
let
Err be
decltype((err)). If the type
Sndr does not satisfy
sender or
if the type
Err does not satisfy
movable-value,
stopped_as_error(sndr, err) is ill-formed
. Otherwise, the expression
stopped_as_error(sndr, err)
is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(stopped_as_error, err, sndr))
except that
sndr is only evaluated once
.Let
sndr and
env be subexpressions
such that
Sndr is
decltype((sndr)) and
Env is
decltype((env)). If
sender-for<Sndr, stopped_as_error_t> is
false,
then the expression
stopped_as_error.transform_sender(sndr, env)
is ill-formed;
otherwise, it is equivalent to:
auto&& [_, err, child] = sndr;
using E = decltype(auto(err));
return let_stopped(
std::forward_like<Sndr>(child),
[err = std::forward_like<Sndr>(err)]() mutable noexcept(is_nothrow_move_constructible_v<E>) {
return just_error(std::move(err));
});
associate tries to associate
a sender with an async scope such that
the scope can track the lifetime of any asynchronous operations
created with the sender
. Let associate-data be the following exposition-only class template:
namespace std::execution {
template<scope_token Token, sender Sender>
struct associate-data {
using wrap-sender =
remove_cvref_t<decltype(declval<Token&>().wrap(declval<Sender>()))>;
using assoc-t = decltype(declval<Token&>().try_associate());
using sender-ref =
unique_ptr<wrap-sender, decltype([](auto* p) noexcept { destroy_at(p); })>;
explicit associate-data(Token t, Sender&& s)
: sndr(t.wrap(std::forward<Sender>(s))),
assoc([&] {
sender-ref guard{addressof(sndr)};
auto assoc = t.try_associate();
if (assoc) {
guard.release();
}
return assoc;
}()) {}
associate-data(const associate-data& other)
noexcept(is_nothrow_copy_constructible_v<wrap-sender> &&
noexcept(other.assoc.try_associate()));
associate-data(associate-data&& other)
noexcept(is_nothrow_move_constructible_v<wrap-sender>)
: associate-data(std::move(other).release()) {}
~associate-data();
pair<assoc-t, sender-ref> release() && noexcept;
private:
associate-data(pair<assoc-t, sender-ref> parts);
union {
wrap-sender sndr;
};
assoc-t assoc;
};
template<scope_token Token, sender Sender>
associate-data(Token, Sender&&) -> associate-data<Token, Sender>;
}
For an
associate-data object
a,
bool(a.assoc) is
true
if and only if
an association was successfully made and is owned by
a.associate-data(const associate-data& other)
noexcept(is_nothrow_copy_constructible_v<wrap-sender> &&
noexcept(other.assoc.try_associate()));
Effects: Initializes
assoc with
other.assoc.try_associate(). If
bool(assoc) is
true,
initializes
sndr with
other.sndr.associate-data(pair<assoc-t, sender-ref> parts);
Effects: Initializes
assoc with
std::move(parts.first). If
bool(assoc) is
true,
initializes
sndr with
std::move(*parts.second).Effects: If
bool(assoc) is
true, destroys
sndr. pair<assoc-t, sender-ref> release() && noexcept;
Effects: Constructs an object
u of type
sender-ref
that is initialized with
addressof(sndr)
if
bool(assoc) is
true and
with
nullptr otherwise,
then returns
pair{std::move(assoc), std::move(u)}. The name
associate denotes a pipeable sender adaptor object
. For subexpressions
sndr and
token:
- If decltype((sndr)) does not satisfy sender, or
remove_cvref_t<decltype((token))>
does not satisfy scope_token, then
associate(sndr, token) is ill-formed.
- Otherwise,
the expression associate(sndr, token)
is expression-equivalent to:
transform_sender(get-domain-early(sndr),
make-sender(associate, associate-data(token, sndr)))
except that sndr is evaluated only once.
The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
associate_t as follows:
namespace std::execution {
template<>
struct impls-for<associate_t> : default-impls {
static constexpr auto get-state = see below;
static constexpr auto start = see below;
template<class Sndr, class... Env>
static consteval void check-types() {
using associate_data_t = remove_cvref_t<data-type<Sndr>>;
using child_type_t = associate_data_t::wrap-sender;
(void)get_completion_signatures<child_type_t, FWD-ENV-T(Env)...>();
}
};
}
The member impls-for<associate_t>::get-state
is initialized with a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(see below) {
auto&& [_, data] = std::forward<Sndr>(sndr);
using associate_data_t = remove_cvref_t<decltype(data)>;
using assoc_t = associate_data_t::assoc-t;
using sender_ref_t = associate_data_t::sender-ref;
using op_t = connect_result_t<typename sender_ref_t::element_type, Rcvr>;
struct op_state {
assoc_t assoc;
union {
Rcvr* rcvr;
op_t op;
};
explicit op_state(pair<assoc_t, sender_ref_t> parts, Rcvr& r)
: assoc(std::move(parts.first)) {
if (assoc) {
::new (voidify(op)) op_t(connect(std::move(*parts.second), std::move(r)));
} else {
rcvr = addressof(r);
}
}
explicit op_state(associate_data_t&& ad, Rcvr& r)
: op_state(std::move(ad).release(), r) {}
explicit op_state(const associate_data_t& ad, Rcvr& r)
requires copy_constructible<associate_data_t>
: op_state(associate_data_t(ad).release(), r) {}
op_state(op_state&&) = delete;
~op_state() {
if (assoc) {
op.~op_t();
}
}
void run() noexcept {
if (assoc) {
start(op);
} else {
set_stopped(std::move(*rcvr));
}
}
};
return op_state{std::forward_like<Sndr>(data), rcvr};
}
The expression in the
noexcept clause of
impls-for<associate_t>::get-state is
(is_same_v<Sndr, remove_cvref_t<Sndr>> ||
is_nothrow_constructible_v<remove_cvref_t<Sndr>, Sndr>) &&
nothrow-callable<connect_t, wrap-sender, Rcvr>
where
wrap-sender is the type
remove_cvref_t<data-type<Sndr>>::wrap-sender.The member impls-for<associate_t>::start
is initialized with a callable object equivalent to the following lambda:
[](auto& state, auto&) noexcept -> void {
state.run();
}
The evaluation of
associate(sndr, token)
may cause side effects observable
via
token's associated async scope object
.stop-when fuses an additional stop token
t
into a sender so that, upon connecting to a receiver
r,
the resulting operation state receives stop requests from both
t and the token returned from
get_stop_token(get_env(r)). The name
stop-when denotes an exposition-only sender adaptor
. For subexpressions
sndr and
token:
If
decltype((sndr)) does not satisfy
sender, or
remove_cvref_t<decltype((token))>
does not satisfy
stoppable_token,
then
stop-when(sndr, token) is ill-formed
.Otherwise,
if
remove_cvref_t<decltype((token))> models
unstoppable_token then
stop-when(sndr, token) is expression-equivalent to
(void)token, sndr,
except that
token and
sndr are indeterminately sequenced
.Otherwise,
stop-when(sndr, token) returns a sender
osndr. If
osndr is connected to a receiver
r,
let
rtoken be the result of
get_stop_token(get_env(r)).If the type of
rtoken models
unstoppable_token then
the effects of connecting
osndr to
r
are equivalent to
connect(write_env(sndr, prop(get_stop_token, token)), r).Otherwise,
the effects of connecting
osndr to
r
are equivalent to
connect(write_env(sndr, prop(get_stop_token, stoken)), r)
where
stoken is an object of
an exposition-only type
stoken-t such that:
- stoken-t models stoppable_token;
- stoken.stop_requested() returns
token.stop_requested() || rtoken.stop_reques-
ted(); - stoken.stop_possible() returns
token.stop_possible() || rtoken.stop_possible(); and
- for types Fn and Init such that both
invocable<Fn> and
constructible_from<Fn, Init>
are modeled,
stoken-t::callback_type<Fn> models
stoppable-callback-for<Fn, stoken-t, Init>.
[
Note 1:
For an object
fn of type
Fn
constructed from a value,
init, of type
Init,
registering
fn using
stoken-t::callback_type<Fn>(stoken, init)
results in an invocation of
fn when
a callback registered with
token or
rtoken would be invoked
. fn is invoked at most once
. —
end note]
spawn_future attempts to associate the given input sender
with the given token's async scope and, on success,
eagerly starts the input sender;
the return value is a sender that, when connected and started,
completes with either
the result of the eagerly-started input sender or with
set_stopped if the input sender was not started
. The name
spawn_future denotes a customization point object
. For subexpressions
sndr,
token, and
env,
- let Sndr be decltype((sndr)),
- let Token be remove_cvref_t<decltype((token))>, and
- let Env be remove_cvref_t<decltype((env))>.
If any of
sender<Sndr>,
scope_token<Token>, or
queryable<Env>
are not satisfied,
the expression
spawn_future(sndr, token, env) is ill-formed
.Let spawn-future-state-base be the exposition-only class template:
namespace std::execution {
template<class Completions>
struct spawn-future-state-base;
template<class... Sigs>
struct spawn-future-state-base<completion_signatures<Sigs...>> {
using variant-t = see below;
variant-t result;
virtual void complete() noexcept = 0;
};
}
Let
Sigs be the pack of arguments to
the
completion_signatures specialization provided as
a parameter to the
spawn-future-state-base class template
. Let
as-tuple be an alias template that
transforms a completion signature
Tag(Args...)
into the tuple specialization
decayed-tuple<Tag, Args...>.If
is_nothrow_constructible_v<decay_t<Arg>, Arg> is
true
for every type
Arg
in every parameter pack
Args
in every completion signature
Tag(Args...)
in
Sigs then
variant-t denotes the type
variant<monostate, tuple<set_stopped_t>, as-tuple<Sigs>...>,
except with duplicate types removed
.Otherwise
variant-t denotes the type
variant<monostate, tuple<set_stopped_t>, tuple<set_error_t, exception_ptr>, as-tuple<Sigs>...>,
except with duplicate types removed
.
Let spawn-future-receiver be the exposition-only class template:
namespace std::execution {
template<class Completions>
struct spawn-future-receiver {
using receiver_concept = receiver_t;
spawn-future-state-base<Completions>* state;
template<class... T>
void set_value(T&&... t) && noexcept {
set-complete<set_value_t>(std::forward<T>(t)...);
}
template<class E>
void set_error(E&& e) && noexcept {
set-complete<set_error_t>(std::forward<E>(e));
}
void set_stopped() && noexcept {
set-complete<set_stopped_t>();
}
private:
template<class CPO, class... T>
void set-complete(T&&... t) noexcept {
constexpr bool nothrow = (is_nothrow_constructible_v<decay_t<T>, T> && ...);
try {
state->result.template emplace<decayed-tuple<CPO, T...>>(CPO{},
std::forward<T>(t)...);
}
catch (...) {
if constexpr (!nothrow) {
using tuple_t = decayed-tuple<set_error_t, exception_ptr>;
state->result.template emplace<tuple_t>(set_error_t{}, current_exception());
}
}
state->complete();
}
};
}
Let
ssource be an lvalue of type
ssource-t. Let
stoken-t be
decltype(ssource.get_token()). Let future-spawned-sender be the alias template:
template<sender Sender, class Env>
using future-spawned-sender =
decltype(write_env(stop-when(declval<Sender>(), declval<stoken-t>()), declval<Env>()));
Let spawn-future-state be the exposition-only class template:
namespace std::execution {
template<class Alloc, scope_token Token, sender Sender, class Env>
struct spawn-future-state
: spawn-future-state-base<completion_signatures_of_t<future-spawned-sender<Sender, Env>>> {
using sigs-t =
completion_signatures_of_t<future-spawned-sender<Sender, Env>>;
using receiver-t =
spawn-future-receiver<sigs-t>;
using op-t =
connect_result_t<future-spawned-sender<Sender, Env>, receiver-t>;
spawn-future-state(Alloc alloc, Sender&& sndr, Token token, Env env)
: alloc(std::move(alloc)),
op(connect(
write_env(stop-when(std::forward<Sender>(sndr), ssource.get_token()), std::move(env)),
receiver-t(this))),
assoc(token.try_associate()) {
if (assoc)
start(op);
else
set_stopped(receiver-t(this));
}
void complete() noexcept override;
void consume(receiver auto& rcvr) noexcept;
void abandon() noexcept;
private:
using assoc-t =
remove_cvref_t<decltype(declval<Token&>().try_associate())>;
Alloc alloc;
ssource-t ssource;
op-t op;
assoc-t assoc;
void destroy() noexcept;
};
template<class Alloc, scope_token Token, sender Sender, class Env>
spawn-future-state(Alloc alloc, Sender&& sndr, Token token, Env env)
-> spawn-future-state<Alloc, Token, Sender, Env>;
}
For purposes of determining the existence of a data race,
complete,
consume, and
abandon
behave as atomic operations (
[intro.multithread])
. These operations on a single object of a type
that is a specialization of
spawn-future-state
appear to occur in a single total order
.void complete() noexcept;
Effects:
- No effects if this invocation of complete happens before
an invocation of consume or abandon on *this;
- otherwise,
if an invocation of consume on *this happens before
this invocation of complete then
there is a receiver, rcvr, registered and
that receiver is completed as if by consume(rcvr);
- otherwise,
destroy is invoked.
void consume(receiver auto& rcvr) noexcept;
Effects:
- If this invocation of consume happens before
an invocation of complete on *this then
rcvr is registered to be completed when
complete is subsequently invoked on *this;
- otherwise,
rcvr is completed as if by:
std::move(this->result).visit(
[&rcvr](auto&& tuple) noexcept {
if constexpr (!same_as<remove_reference_t<decltype(tuple)>, monostate>) {
apply([&rcvr](auto cpo, auto&&... vals) {
cpo(std::move(rcvr), std::move(vals)...);
}, std::move(tuple));
}
});
Effects:
- If this invocation of abandon happens before
an invocation of complete on *this then
equivalent to:
ssource.request_stop();
- otherwise,
destroy is invoked.
Effects: Equivalent to:
auto associated = std::move(this->associated);
{
using traits = allocator_traits<Alloc>::template rebind_traits<spawn-future-state>;
typename traits::allocator_type alloc(std::move(this->alloc));
traits::destroy(alloc, this);
traits::deallocate(alloc, this, 1);
}
The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
spawn_future_t as follows:
namespace std::execution {
template<>
struct impls-for<spawn_future_t> : default-impls {
static constexpr auto start = see below;
};
}
The member impls-for<spawn_future_t>::start
is initialized with a callable object equivalent to the following lambda:
[](auto& state, auto& rcvr) noexcept -> void {
state->consume(rcvr);
}
For the expression
spawn_future(sndr, token, env)
let
new_sender be the expression
token.wrap(sndr) and
let
alloc and
senv be defined as follows:
- if the expression get_allocator(env) is well-formed, then
alloc is the result of get_allocator(env) and
senv is the expression env;
- otherwise,
if the expression get_allocator(get_env(new_sender)) is well-formed, then
alloc is the result of get_allocator(get_env(new_sender)) and
senv is the expression
JOIN-ENV(prop(get_allocator, alloc), env);
- otherwise,
alloc is allocator<void>() and
senv is the expression env.
The expression spawn_future(sndr, token, env)
has the following effects:
Uses
alloc to allocate and construct an object
s of type
decltype(spawn-future-state(alloc, token.wrap(sndr), token, senv))
from
alloc,
token.wrap(sndr),
token, and
senv. If an exception is thrown then
any constructed objects are destroyed and
any allocated memory is deallocated
.Constructs an object
u of
a type that is a specialization of
unique_ptr such that:
- u.get() is equal to the address of s, and
- u.get_deleter()(u.release()) is equivalent to
u.release()->abandon().
Returns
make-sender(spawn_future, std::move(u)).
The expression
spawn_future(sndr, token) is expression-equivalent to
spawn_future(sndr, token, execution::env<>()).this_thread::sync_wait and
this_thread::sync_wait_with_variant
are used
to block the current thread of execution
until the specified sender completes and
to return its async result
. sync_wait mandates
that the input sender has exactly one value completion signature
. Let sync-wait-env be the following exposition-only class type:
namespace std::this_thread {
struct sync-wait-env {
execution::run_loop* loop;
auto query(execution::get_scheduler_t) const noexcept {
return loop->get_scheduler();
}
auto query(execution::get_delegation_scheduler_t) const noexcept {
return loop->get_scheduler();
}
};
}
Let
sync-wait-result-type and
sync-wait-with-variant-result-type
be exposition-only alias templates defined as follows:
namespace std::this_thread {
template<execution::sender_in<sync-wait-env> Sndr>
using sync-wait-result-type =
optional<execution::value_types_of_t<Sndr, sync-wait-env, decayed-tuple,
type_identity_t>>;
template<execution::sender_in<sync-wait-env> Sndr>
using sync-wait-with-variant-result-type =
optional<execution::value_types_of_t<Sndr, sync-wait-env>>;
}
The name
this_thread::sync_wait denotes a customization point object
. For a subexpression
sndr, let
Sndr be
decltype((sndr)). The expression
this_thread::sync_wait(sndr)
is expression-equivalent to the following,
except that
sndr is evaluated only once:
apply_sender(get-domain-early(sndr), sync_wait, sndr)
Mandates:
The type
sync-wait-result-type<Sndr> is well-formed
.same_as<decltype(e), sync-wait-result-type<Sndr>>
is
true, where
e is the
apply_sender expression above
.
Let sync-wait-state and sync-wait-receiver
be the following exposition-only class templates:
namespace std::this_thread {
template<class Sndr>
struct sync-wait-state {
execution::run_loop loop;
exception_ptr error;
sync-wait-result-type<Sndr> result;
};
template<class Sndr>
struct sync-wait-receiver {
using receiver_concept = execution::receiver_t;
sync-wait-state<Sndr>* state;
template<class... Args>
void set_value(Args&&... args) && noexcept;
template<class Error>
void set_error(Error&& err) && noexcept;
void set_stopped() && noexcept;
sync-wait-env get_env() const noexcept { return {&state->loop}; }
};
}
template<class... Args>
void set_value(Args&&... args) && noexcept;
Effects: Equivalent to:
try {
state->result.emplace(std::forward<Args>(args)...);
} catch (...) {
state->error = current_exception();
}
state->loop.finish();
template<class Error>
void set_error(Error&& err) && noexcept;
Effects: Equivalent to:
state->error = AS-EXCEPT-PTR(std::forward<Error>(err));
state->loop.finish();
void set_stopped() && noexcept;
Effects: Equivalent to
state->loop.finish(). For a subexpression
sndr, let
Sndr be
decltype((sndr)). If
sender_to<Sndr, sync-wait-receiver<Sndr>>
is
false,
the expression
sync_wait.apply_sender(sndr) is ill-formed;
otherwise, it is equivalent to:
sync-wait-state<Sndr> state;
auto op = connect(sndr, sync-wait-receiver<Sndr>{&state});
start(op);
state.loop.run();
if (state.error) {
rethrow_exception(std::move(state.error));
}
return std::move(state.result);
The behavior of
this_thread::sync_wait(sndr) is undefined unless:
It blocks the current thread of execution (
[defns.block])
with forward progress guarantee delegation (
[intro.progress])
until the specified sender completes
. [
Note 1:
The default implementation of
sync_wait achieves
forward progress guarantee delegation by providing a
run_loop scheduler
via the
get_delegation_scheduler query
on the
sync-wait-receiver's environment
. The
run_loop is driven by the current thread of execution
. —
end note]
It returns the specified sender's async results as follows:
For a value completion,
the result datums are returned in
a
tuple in an engaged
optional object
.For an error completion, an exception is thrown
.For a stopped completion, a disengaged
optional object is returned
.
The name
this_thread::sync_wait_with_variant denotes
a customization point object
. For a subexpression
sndr,
let
Sndr be
decltype(into_variant(sndr)). The expression
this_thread::sync_wait_with_variant(sndr)
is expression-equivalent to the following,
except
sndr is evaluated only once:
apply_sender(get-domain-early(sndr), sync_wait_with_variant, sndr)
Mandates:
The type
sync-wait-with-variant-result-type<Sndr>
is well-formed
.same_as<decltype(e), sync-wait-with-variant-result-type<Sndr>>
is
true, where
e is the
apply_sender expression above
.
The expression sync_wait_with_variant.apply_sender(sndr) is equivalent to:
using result_type = sync-wait-with-variant-result-type<Sndr>;
if (auto opt_value = sync_wait(into_variant(sndr))) {
return result_type(std::move(get<0>(*opt_value)));
}
return result_type(nullopt);
The behavior of
this_thread::sync_wait_with_variant(sndr)
is undefined unless:
It blocks the current thread of execution (
[defns.block])
with forward progress guarantee delegation (
[intro.progress])
until the specified sender completes
. [
Note 1:
The default implementation of
sync_wait_with_variant achieves
forward progress guarantee delegation by relying on
the forward progress guarantee delegation provided by
sync_wait. —
end note]
It returns the specified sender's async results as follows:
For a value completion,
the result datums are returned in an engaged
optional object
that contains a
variant of
tuples
.For an error completion, an exception is thrown
.For a stopped completion, a disengaged
optional object is returned
.
spawn attempts to associate the given input sender with
the given token's async scope and, on success,
eagerly starts the input sender
. The name
spawn denotes a customization point object
. For subexpressions
sndr,
token, and
env,
- let Sndr be decltype((sndr)),
- let Token be remove_cvref_t<decltype((token))>, and
- let Env be remove_cvref_t<decltype((env))>.
If any of
sender<Sndr>,
scope_token<Token>, or
queryable<Env>
are not satisfied,
the expression
spawn(sndr, token, env) is ill-formed
.Let spawn-state-base be the exposition-only class:
namespace std::execution {
struct spawn-state-base {
virtual void complete() noexcept = 0;
};
}
Let spawn-receiver be the exposition-only class:
namespace std::execution {
struct spawn-receiver {
using receiver_concept = receiver_t;
spawn-state-base* state;
void set_value() && noexcept { state->complete(); }
void set_stopped() && noexcept { state->complete(); }
};
}
Let spawn-state be the exposition-only class template:
namespace std::execution {
template<class Alloc, scope_token Token, sender Sender>
struct spawn-state : spawn-state-base {
using op-t = connect_result_t<Sender, spawn-receiver>;
spawn-state(Alloc alloc, Sender&& sndr, Token token);
void complete() noexcept override;
void run() noexcept;
private:
using assoc-t =
remove_cvref_t<decltype(declval<Token&>().try_associate())>;
Alloc alloc;
op-t op;
assoc-t assoc;
};
}
spawn-state(Alloc alloc, Sender&& sndr, Token token);
Effects: Initializes
alloc with
std::move(alloc),
op with
connect(std::move(sndr), spawn-receiver(this)), and
assoc with
token.try_associate(). Effects: Equivalent to:
if (assoc)
start(op);
else
complete();
void complete() noexcept override;
Effects: Equivalent to:
auto assoc = std::move(this->assoc);
{
using traits = allocator_traits<Alloc>::template rebind_traits<spawn-state>;
typename traits::allocator_type alloc(this->alloc);
traits::destroy(alloc, this);
traits::deallocate(alloc, this, 1);
}
For the expression
spawn(sndr, token, env)
let
new_sender be the expression
token.wrap(sndr) and
let
alloc and
senv be defined as follows:
- if the expression get_allocator(env) is well-formed, then
alloc is the result of get_allocator(env) and
senv is the expression env,
- otherwise
if the expression get_allocator(get_env(new_sender)) is well-formed, then
alloc is the result of get_allocator(get_env(new_sender)) and
senv is the expression JOIN-ENV(prop(get_allocator, alloc), env),
- otherwise
alloc is allocator<void>() and
senv is the expression env.
The expression
spawn(sndr, token, env) is of type
void and
has the following effects:
- Uses alloc to allocate and construct an object o of type
decltype(spawn-state(alloc, write_env(token.wrap(sndr), senv), token))
from alloc, write_env(token.wrap(sndr), senv), and token
and then
invokes o.run().
If an exception is thrown then
any constructed objects are destroyed and any allocated memory is deallocated
.
The expression
spawn(sndr, token) is expression-equivalent to
spawn(sndr, token, execution::env<>()).