33 Execution control library [exec]
as_awaitable transforms an object into one
that is awaitable within a particular coroutine
. Subclause [exec.
coro.
util] makes use of
the following exposition-only entities:
namespace std::execution {
template<class Sndr, class Promise>
concept awaitable-sender =
single-sender<Sndr, env_of_t<Promise>> &&
sender_to<Sndr, awaitable-receiver> &&
requires (Promise& p) {
{ p.unhandled_stopped() } -> convertible_to<coroutine_handle<>>;
};
template<class Sndr>
concept has-queryable-await-completion-adaptor =
sender<Sndr> &&
requires(Sndr&& sender) {
get_await_completion_adaptor(get_env(sender));
};
template<class Sndr, class Promise>
class sender-awaitable;
}
The type sender-awaitable<Sndr, Promise> is equivalent to:
namespace std::execution {
template<class Sndr, class Promise>
class sender-awaitable {
struct unit {};
using value-type =
single-sender-value-type<Sndr, env_of_t<Promise>>;
using result-type =
conditional_t<is_void_v<value-type>, unit, value-type>;
struct awaitable-receiver;
variant<monostate, result-type, exception_ptr> result{};
connect_result_t<Sndr, awaitable-receiver> state;
public:
sender-awaitable(Sndr&& sndr, Promise& p);
static constexpr bool await_ready() noexcept { return false; }
void await_suspend(coroutine_handle<Promise>) noexcept { start(state); }
value-type await_resume();
};
}
awaitable-receiver is equivalent to:
struct awaitable-receiver {
using receiver_concept = receiver_t;
variant<monostate, result-type, exception_ptr>* result-ptr;
coroutine_handle<Promise> continuation;
};
Let
rcvr be an rvalue expression of type
awaitable-receiver,
let
crcvr be a const lvalue that refers to
rcvr,
let
vs be a pack of subexpressions, and
let
err be an expression of type
Err. Then:
- If constructible_from<result-type, decltype((vs))...>
is satisfied,
the expression set_value(
rcvr, vs...) is equivalent to:
try {
rcvr.result-ptr->template emplace<1>(vs...);
} catch(...) {
rcvr.result-ptr->template emplace<2>(current_exception());
}
rcvr.continuation.resume();
Otherwise,
set_value(rcvr, vs...) is ill-formed
. - The expression set_error(rcvr, err) is equivalent to:
rcvr.result-ptr->template emplace<2>(AS-EXCEPT-PTR(err));
rcvr.continuation.resume();
- The expression set_stopped(rcvr) is equivalent to:
static_cast<coroutine_handle<>>(rcvr.continuation.promise().unhandled_stopped()).resume();
- For any expression tag
whose type satisfies forwarding-query and
for any pack of subexpressions as,
get_env(crcvr).query(tag, as...) is expression-equivalent to:
tag(get_env(as_const(crcvr.continuation.promise())), as...)
sender-awaitable(Sndr&& sndr, Promise& p);
Effects: Initializes state with
connect(std::forward<Sndr>(sndr),
awaitable-receiver{addressof(result), coroutine_handle<Promise>::from_promise(p)})
value-type await_resume();
Effects: Equivalent to:
if (result.index() == 2)
rethrow_exception(get<2>(result));
if constexpr (!is_void_v<value-type>)
return std::forward<value-type>(get<1>(result));
as_awaitable is a customization point object
. For subexpressions
expr and
p
where
p is an lvalue,
Expr names the type
decltype((expr)) and
Promise names the type
decay_t<decltype((p))>,
as_awaitable(expr, p) is expression-equivalent to,
except that the evaluations of
expr and
p
are indeterminately sequenced:
expr.as_awaitable(p) if that expression is well-formed
. Mandates:
is-awaitable<A, Promise> is
true,
where
A is the type of the expression above
. Otherwise,
(void(p), expr)
if
is-awaitable<Expr, U> is
true,
where
U is an unspecified class type
that is not
Promise and
that lacks a member named
await_transform.Preconditions:
is-awaitable<Expr, Promise> is
true and
the expression
co_await expr
in a coroutine with promise type
U is expression-equivalent to
the same expression in a coroutine with promise type
Promise. Otherwise,
sender-awaitable{adapted-expr, p}
if
has-queryable-await-completion-adaptor<Expr>
and
awaitable-sender<decltype((adapted-expr)), Promise>
are both satisfied, where
adapted-expr is
get_await_completion_adaptor(get_env(expr))(expr),
except that
expr is evaluated only once
.Otherwise,
(void(p), expr).
with_awaitable_senders,
when used as the base class of a coroutine promise type,
makes senders awaitable in that coroutine type
. In addition, it provides a default implementation of
unhandled_stopped
such that if a sender completes by calling
set_stopped,
it is treated as if an uncatchable "stopped" exception were thrown
from the
await-expression. [
Note 1:
The coroutine is never resumed, and
the
unhandled_stopped of the coroutine caller's promise type is called
. —
end note]
namespace std::execution {
template<class-type Promise>
struct with_awaitable_senders {
template<class OtherPromise>
requires (!same_as<OtherPromise, void>)
void set_continuation(coroutine_handle<OtherPromise> h) noexcept;
coroutine_handle<> continuation() const noexcept { return continuation; }
coroutine_handle<> unhandled_stopped() noexcept {
return stopped-handler(continuation.address());
}
template<class Value>
see below await_transform(Value&& value);
private:
[[noreturn]] static coroutine_handle<>
default-unhandled-stopped(void*) noexcept {
terminate();
}
coroutine_handle<> continuation{};
coroutine_handle<> (*stopped-handler)(void*) noexcept =
&default-unhandled-stopped;
};
}
template<class OtherPromise>
requires (!same_as<OtherPromise, void>)
void set_continuation(coroutine_handle<OtherPromise> h) noexcept;
Effects: Equivalent to:
continuation = h;
if constexpr ( requires(OtherPromise& other) { other.unhandled_stopped(); } ) {
stopped-handler = [](void* p) noexcept -> coroutine_handle<> {
return coroutine_handle<OtherPromise>::from_address(p)
.promise().unhandled_stopped();
};
} else {
stopped-handler = &default-unhandled-stopped;
}
Effects: Equivalent to:
return as_awaitable(std::forward<Value>(value), static_cast<Promise&>(*this));
affine_on adapts a sender into one that completes on
the specified scheduler
. If the algorithm determines that the adapted sender already completes
on the correct scheduler it can avoid any scheduling operation
.The name
affine_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,
affine_on(sndr, sch)
is ill-formed
.Otherwise, the expression
affine_on(sndr, sch) is
expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(affine_on, sch, sndr))
except that
sndr is evaluated only once
.The exposition-only class template
impls-for (
[exec.snd.expos])
is specialized for
affine_on_t as follows:
namespace std::execution {
template<>
struct impls-for<affine_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
out_sndr be a subexpression denoting a sender
returned from
affine_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 to
out_rcvr. Calling
start(op) will 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 the current execution resource is the same as the execution
resource associated with
sch, the completion operation on
out_rcvr may be called before
start(op) completes
. If scheduling onto
sch fails, an error completion on
out_rcvr shall be executed on an unspecified execution
agent
.namespace std::execution {
class inline_scheduler {
class inline-sender;
template<receiver R>
class inline-state;
public:
using scheduler_concept = scheduler_t;
constexpr inline-sender schedule() noexcept { return {}; }
constexpr bool operator==(const inline_scheduler&) const noexcept = default;
};
}
All objects of type
inline_scheduler are equal
. inline-sender is an exposition-only type that satisfies
sender. The type
completion_signatures_of_t<inline-sender>
is
completion_signatures<set_value_t()>.Let
sndr be an expression of type
inline-sender,
let
rcvr be an expression such that
receiver_of<decltype((rcvr)), CS> is
true
where
CS is
completion_signatures<set_value_t()>,
then:
- the expression connect(sndr, rcvr) has
type inline-state<remove_cvref_t<decltype((rcvr))>>
and is potentially-throwing if and only if
((void)sndr, auto(rcvr)) is potentially-throwing, and
- the expression
get_completion_scheduler<set_value_t>(get_env(sndr)) has
type inline_scheduler and is potentially-throwing
if and only if get_env(sndr) is potentially-throwing.
Let
o be a non-
const lvalue of type
inline-state<Rcvr>, and let
REC(o) be
a non-
const lvalue reference to an object of type
Rcvr that
was initialized with the expression
rcvr passed to an
invocation of
connect that returned
o, then:
- the object to which REC(o) refers remains valid for
the lifetime of the object to which o refers, and
- the expression start(o) is equivalent to
set_value(std::move(REC(o))).
namespace std::execution {
class task_scheduler {
class ts-sender;
template<receiver R>
class state;
public:
using scheduler_concept = scheduler_t;
template<class Sch, class Allocator = allocator<void>>
requires (!same_as<task_scheduler, remove_cvref_t<Sch>>)
&& scheduler<Sch>
explicit task_scheduler(Sch&& sch, Allocator alloc = {});
ts-sender schedule();
friend bool operator==(const task_scheduler& lhs, const task_scheduler& rhs)
noexcept;
template<class Sch>
requires (!same_as<task_scheduler, Sch>)
&& scheduler<Sch>
friend bool operator==(const task_scheduler& lhs, const Sch& rhs) noexcept;
private:
shared_ptr<void> sch_;
};
}
Given an object
s of type
task_scheduler, let
SCHED(s) be the object owned by
s.sch_. template<class Sch, class Allocator = allocator<void>>
requires(!same_as<task_scheduler, remove_cvref_t<Sch>>) && scheduler<Sch>
explicit task_scheduler(Sch&& sch, Allocator alloc = {});
Effects: Initialize
sch_ with
allocate_shared<remove_cvref_t<Sch>>(alloc, std::forward<Sch>(sch)). Recommended practice: Implementations should avoid the use of dynamically
allocated memory for small scheduler objects
. Remarks: Any allocations performed by construction of
ts-sender or
state objects resulting from calls on
*this are
performed using a copy of
alloc. Effects: Returns an object of type
ts-sender containing a sender
initialized with
schedule(SCHED(*this)). bool operator==(const task_scheduler& lhs, const task_scheduler& rhs) noexcept;
Effects: Equivalent to: return lhs == SCHED(rhs);
template<class Sch>
requires (!same_as<task_scheduler, Sch>)
&& scheduler<Sch>
bool operator==(const task_scheduler& lhs, const Sch& rhs) noexcept;
Returns:
false if the type of
SCHED(lhs) is not
Sch,
otherwise
SCHED(lhs) == rhs. namespace std::execution {
class task_scheduler::ts-sender {
public:
using sender_concept = sender_t;
template<receiver Rcvr>
state<Rcvr> connect(Rcvr&& rcvr);
};
}
ts-sender is an exposition-only class that models
sender (
[exec.snd]) and for which
completion_signatures_of_t<ts-sender> denotes:
completion_signatures<
set_value_t(),
set_error_t(error_code),
set_error_t(exception_ptr),
set_stopped_t()>
Let
sch be an object of type
task_scheduler
and let
sndr be an object of type
ts-sender obtained
from
schedule(sch). Then
get_completion_scheduler<set_value_t>(get_env(sndr)) == sch
is
true. The object
SENDER(sndr) is the sender object contained by
sndr or an object move constructed from it
.template<receiver Rcvr>
state<Rcvr> connect(Rcvr&& rcvr);
Effects: Let
r be an object of a type that models
receiver
and whose completion handlers result in invoking the corresponding
completion handlers of
rcvr or copy thereof
. Returns an object of type
state<Rcvr> containing
an operation state object initialized with
connect(SENDER(*this),
std::move(r)).namespace std::execution {
template<receiver R>
class task_scheduler::state {
public:
using operation_state_concept = operation_state_t;
void start() & noexcept;
};
}
state is an exposition-only class template whose
specializations model
operation_state (
[exec.opstate])
. Effects: Equivalent to
start(st) where
st is the operation
state object contained by
*this. The
task class template represents a sender that can
be used as the return type of coroutines
. The first template parameter
T defines the type of the value
completion datum (
[exec.async.ops]) if
T is not
void. Otherwise, there are no value completion datums
. Inside coroutines returning
task<T, E> the operand of
co_return (if any) becomes the argument of
set_value. The second template parameter
Environment is used to customize
the behavior of
task.namespace std::execution {
template<class T, class Environment>
class task {
template<receiver Rcvr>
class state;
public:
using sender_concept = sender_t;
using completion_signatures = see below;
using allocator_type = see below;
using scheduler_type = see below;
using stop_source_type = see below;
using stop_token_type = decltype(declval<stop_source_type>().get_token());
using error_types = see below;
class promise_type;
task(task&&) noexcept;
~task();
template<receiver Rcvr>
state<Rcvr> connect(Rcvr&& rcvr);
private:
coroutine_handle<promise_type> handle;
};
}
task<T, E> models
sender (
[exec.snd])
if
T is
void, a reference type, or a
cv-unqualified
non-array object type and
E is a class type
. Otherwise a program that instantiates the definition of
task<T, E>
is ill-formed
.The nested types of
task template specializations
are determined based on the
Environment parameter:
allocator_type is
Environment::allocator_type
if that
qualified-id is valid and denotes a type,
allocator<byte> otherwise
. scheduler_type is
Environment::scheduler_type
if that
qualified-id is valid and denotes a type,
task_scheduler otherwise
. stop_source_type is
Environment::stop_source_type
if that
qualified-id is valid and denotes a type,
inplace_stop_source otherwise
. error_types is
Environment::error_types if
that
qualified-id is valid and denotes a type,
completion_signatures<set_error_t(exception_ptr)> otherwise
.
A program is ill-formed if
error_types is not a
specialization of
completion_signatures<ErrorSigs...> or
ErrorSigs contains an element which is not of the form
set_error_t(E) for some type
E.The type alias
completion_signatures is a specialization
of
execution::completion_signatures with the template
arguments (in unspecified order):
- set_value_t() if T is void,
and set_value_t(T) otherwise;
- template arguments of the specialization of
execution::completion_signatures denoted by error_types;
and
- set_stopped_t().
allocator_type shall meet the
Cpp17Allocator
requirements
. task(task&& other) noexcept;
Effects: Initializes
handle with
exchange(other.handle,
{}). Effects: Equivalent to:
if (handle)
handle.destroy();
template<receiver Rcvr>
state<Rcvr> connect(Rcvr&& recv);
Preconditions:
bool(handle) is
true. Effects: Equivalent to:
return state<Rcvr>(exchange(handle, {}), std::forward<Rcvr>(recv));
namespace std::execution {
template<class T, class Environment>
template<receiver Rcvr>
class task<T, Environment>::state {
public:
using operation_state_concept = operation_state_t;
template<class R>
state(coroutine_handle<promise_type> h, R&& rr);
~state();
void start() & noexcept;
private:
using own-env-t = see below;
coroutine_handle<promise_type> handle;
remove_cvref_t<Rcvr> rcvr;
own-env-t own-env;
Environment environment;
};
}
The type
own-env-t is
Environment::template
env_type<decltype(get_env(declval<Rcvr>()))> if that
qualified-id is valid and denotes a type,
env<> otherwise
.template<class R>
state(coroutine_handle<promise_type> h, R&& rr);
Effects: Initializes
handle with std::move(h);
rcvr with std::forward<R>(rr);
own-env
with
own-env-t(get_env(rcvr)) if that expression
is valid and
own-env-t() otherwise
. If neither of these expressions is valid, the program is ill-formed
.environment with
Environment(own-env) if that expression is
valid, otherwise
Environment(get_env(rcvr))
if this expression is valid, otherwise
Environment(). If neither of these expressions is valid, the program is ill-formed
.
Effects: Equivalent to:
if (handle)
handle.destroy();
Effects: Let
prom be the object
handle.promise(). Associates
STATE(prom),
RCVR(prom), and
SCHED(prom)
with
*this as follows:
SCHED(prom) is the object initialized
with
scheduler_type(get_scheduler(get_env(rcvr)))
if that expression is valid and
scheduler_type() otherwise
. If neither of these expressions is valid, the program is ill-formed
.
Let
st be
get_stop_token(get_env(rcvr)). Initializes
prom.token and
prom.source such that
- prom.token.stop_requested() returns
st.stop_requested();
- prom.token.stop_possible() returns
st.stop_possible(); and
- for types Fn and Init such that both
invocable<Fn> and
constructible_from<Fn, Init> are modeled,
stop_token_type::callback_type<Fn> models
stoppable-callback-for<Fn, stop_token_type, Init>.
After that invokes
handle.resume().namespace std::execution {
template<class T, class Environment>
class task<T, Environment>::promise_type {
public:
template<class... Args>
promise_type(const Args&... args);
task get_return_object() noexcept;
auto initial_suspend() noexcept;
auto final_suspend() noexcept;
void uncaught_exception();
coroutine_handle<> unhandled_stopped();
void return_void();
template<class V>
void return_value(V&& value);
template<class E>
unspecified yield_value(with_error<E> error);
template<class A>
auto await_transform(A&& a);
template<class Sch>
auto await_transform(change_coroutine_scheduler<Sch> sch);
unspecified get_env() const noexcept;
template<class... Args>
void* operator new(size_t size, Args&&... args);
void operator delete(void* pointer, size_t size) noexcept;
private:
using error-variant = see below;
allocator_type alloc;
stop_source_type source;
stop_token_type token;
optional<T> result;
error-variant errors;
};
}
Let
prom be an object of
promise_type
and let
tsk be the
task object
created by
prom.get_return_object(). The description below
refers to objects
STATE(prom),
RCVR(prom),
and
SCHED(prom)
associated with
tsk
during evaluation of
task::state<Rcvr>::start
for some receiver
Rcvr.error-variant is a
variant<monostate,
remove_cvref_t<E>...>, with duplicate types removed, where
E...
are the parameter types of the template arguments of the specialization of
execution::completion_signatures denoted by
error_types. template<class... Args>
promise_type(const Args&... args);
Mandates: The first parameter of type
allocator_arg_t (if any) is not
the last parameter
. Effects: If
Args contains an element of type
allocator_arg_t
then
alloc is initialized with the corresponding next
element of
args. Otherwise,
alloc is initialized with
allocator_type().task get_return_object() noexcept;
Returns: A
task object whose member
handle is
coroutine_handle<promise_type>::from_promise(*this). auto initial_suspend() noexcept;
Returns: An awaitable object of unspecified type (
[expr.await]) whose
member functions arrange for
- the calling coroutine to be suspended,
- the coroutine to be resumed on an execution agent of the
execution resource associated with SCHED(*this).
auto final_suspend() noexcept;
Returns: An awaitable object of unspecified type (
[expr.await]) whose
member functions arrange for the completion of the asynchronous
operation associated with
STATE(*this) by invoking:
- set_error(std::move(RCVR(*this)), std::move(e))
if errors.index() is greater than zero and
e is the value held by errors, otherwise
- set_value(std::move(RCVR(*this))) if is_void<T> is true,
and otherwise
- set_value(std::move(RCVR(*this)), *result).
template<class Err>
auto yield_value(with_error<Err> err);
Mandates:
std::move(err.error) is convertible to exactly one of the
set_error_t argument types of
error_types. Returns: An awaitable object of unspecified type (
[expr.await]) whose
member functions arrange for the calling coroutine to be suspended
and then completes the asynchronous operation associated with
STATE(*this) by invoking
set_error(std::move(RCVR(*this)),
Cerr(std::move(err.error))). Returns: If
same_as<inline_scheduler, scheduler_type> is
true
returns
as_awaitable(std::forward<Sender>(sndr), *this);
otherwise returns
as_awaitable(affine_on(std::forward<Sender>(sndr), SCHED(*this)), *this). Effects: Equivalent to:
return await_transform(just(exchange(SCHED(*this), scheduler_type(sch.scheduler))), *this);
void uncaught_exception();
Effects: If the signature
set_error_t(exception_ptr) is not an element
of
error_types, calls
terminate() (
[except.terminate])
. Otherwise, stores
current_exception() into
errors.coroutine_handle<> unhandled_stopped();
Effects: Completes the asynchronous operation associated with
STATE(*this)
by invoking
set_stopped(std::move(RCVR(*this))). Returns:
noop_coroutine(). unspecified get_env() const noexcept;
Returns: An object
env such that queries are forwarded as follows:
env.query(get_scheduler) returns
scheduler_type(SCHED(*this)). env.query(get_allocator) returns
alloc. env.query(get_stop_token) returns
token. For any other query
q and arguments
a... a
call to
env.query(q, a...) returns
STATE(*this). environment.query(q, a...) if this expression
is well-formed and
forwarding_query(q) is well-formed and is
true. Otherwise
env.query(q, a...) is ill-formed
.
template<class... Args>
void* operator new(size_t size, const Args&... args);
If there is no parameter with type
allocator_arg_t then let
alloc be
allocator_type(). Otherwise, let
arg_next be the parameter
following the first
allocator_arg_t parameter,
and let
alloc be
allocator_type(arg_next). Let
PAlloc be
allocator_traits<allocator_type>::template
rebind_alloc<U>, where
U is an unspecified type
whose size and alignment are both
__STDCPP_DEFAULT_NEW_ALIGNMENT__.Mandates:
The first parameter of type
allocator_arg_t (if any) is not the last parameter
.allocator_type(arg_next) is a valid expression if there is a parameter
of type
allocator_arg_t. allocator_traits<PAlloc>::pointer is a pointer type
.
Effects: Initializes an allocator
palloc of type
PAlloc with
alloc. Uses
palloc to allocate storage for the
smallest array of
U sufficient to provide storage for a
coroutine state of size
size, and unspecified additional
state necessary to ensure that
operator delete can later
deallocate this memory block with an allocator equal to
palloc.Returns: A pointer to the allocated storage
. void operator delete(void* pointer, size_t size) noexcept;
Preconditions:
pointer was returned from an invocation of the above overload
of
operator new with a size argument equal to
size. Effects: Deallocates the storage pointed to by
pointer using an
allocator equal to that used to allocate it
.