33 Execution control library [exec]
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...).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-or-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:
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
. namespace std::execution {
template<class Tag>
concept completion-tag =
same_as<Tag, set_value_t> || same_as<Tag, set_error_t> || same_as<Tag, set_stopped_t>;
template<template<class...> class T, class... Args>
concept valid-specialization =
requires { typename T<Args...>; };
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 Tag>
struct impls-for : default-impls {};
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, 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;
};
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;
};
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>>;
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...);
}
};
template<class Sndr, class Env>
using completion-signatures-for = see below;
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>
auto get_completion_signatures(this Self&& self, Env&& env) noexcept
-> completion-signatures-for<Self, Env> {
return {};
}
};
}
The default template argument for the
Data template parameter
denotes an unspecified empty trivially copyable class type
that models
semiregular.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 constructor of
basic-state is:
is_nothrow_move_constructible_v<Rcvr> &&
nothrow-callable<decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>
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. 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>()))
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>
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 empty_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 std::forward_like<Sndr>(data);
}
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)...);
}
For a subexpression
sndr let
Sndr be
decltype((sndr)). Let
rcvr be a receiver
with an associated environment of type
Env
such that
sender_in<Sndr, Env> is
true. completion-signatures-for<Sndr, Env> denotes
a specialization of
completion_signatures,
the set of whose template arguments correspond to
the set of completion operations that are potentially evaluated
as a result of starting (
[exec.async.ops])
the operation state that results from connecting
sndr and
rcvr. When
sender_in<Sndr, Env> is
false,
the type denoted by
completion-signatures-for<Sndr, Env>,
if any, is not a specialization of
completion_signatures.Recommended practice: When
sender_in<Sndr, Env> is
false,
implementations are encouraged to use the type
denoted by
completion-signatures-for<Sndr, Env>
to communicate to users why
. template<sender Sndr, queryable Env>
constexpr auto write-env(Sndr&& sndr, Env&& env);
write-env is an exposition-only sender adaptor that,
when connected with a receiver
rcvr,
connects the adapted sender with a receiver
whose execution environment is the result of
joining the
queryable argument
env
to the result of
get_env(rcvr). Let
write-env-t be an exposition-only empty class type
.Returns: make-sender(write-env-t(), std::forward<Env>(env), std::forward<Sndr>(sndr))
Remarks: The exposition-only class template
impls-for (
[exec.snd.general])
is specialized for
write-env-t as follows:
template<>
struct impls-for<write-env-t> : default-impls {
static constexpr auto get-env =
[](auto, const auto& state, const auto& rcvr) noexcept {
return JOIN-ENV(state, get_env(rcvr));
};
};