This paper aims to improve the user experience of the sender framework by giving users immediate feedback about incorrect sender expressions.
A relatively minor change to how sender completion signatures are computed, and a trivial change to the sender adaptor algorithms makes it possible for the majority of sender expressions to be type-checked immediately, when the expression is constructed, rather than when it is connected to a receiver (the status quo).
Below are the specific changes this paper proposes in order to improve the diagnostics emitted by sender-based codes:
Define a "non-dependent sender" to be one whose completions are knowable without an environment.
Add support for calling get_completion_signatures
without an environment argument.
Change the definition of the
completion_signatures_of_t
alias template to support
querying a sender's non-dependent signatures, if such exist.
Extend the awaitable helper concepts to support querying a type
whether it is awaitable in an arbitrary coroutine (without knowing the
promise type). For example, anything that implements the awaiter
interface (await_ready
, await_suspend
,
await_resume
) is awaitable in any coroutine, and should
function as a non-dependent sender.
Require the sender adaptor algorithms to preserve the "non-dependent sender" property wherever possible.
Add "Mandates:" paragraphs to the sender adaptor algorithms to require them to hard-error when passed non-dependent senders that fail type-checking.
Extend the eager type checking of the let_
family of
algorithms to hard-error if the user passes a lambda that does not
return a sender type.
For any algorithm that eagerly connect
s a sender
(e.g., sync_wait
, split
), hard-error
(i.e. static_assert
) if the sender fails to
type-check rather than SFINAE-ing the overload away.
Specify that run_loop
's schedule sender is
non-dependent.
R3:
Rebase the paper on the current standard working draft.
Remove section respecifying
transform_completion_signatures
to propagate type errors. A
separate paper (P3557) addresses
the issue of type errors during the computation of completion
signatures.
Specify stopped_as_optional
to mandate that its
child sender satisfies the single-sender
concept,
and change the single-sender
concept so that it
works properly with non-dependent senders.
Add requirement to [exec.snd.general] that ensures user-defined customizations of sender algorithms produce non-dependent senders when the default implementation would.
Specify the exposition-only basic-sender
helper to support the creation of non-dependent senders. (This change
includes the proposed resolution for cplusplus/sender-receiver#307.)
Update the exposition-only sender-of
concept to work with non-dependent senders (i.e.
sender-of<Sndr, int>
subsumes
sender_in<Sndr>
).
Specify that the sender returned by calling schedule
on run_loop
's scheduler is non-dependent.
R2:
Remove the sender_in<Sndr, Env...>
constraint
on the completion_signatures_of_t<Sndr, Env...>
alias.
Specify get_completion_signatures(sndr, env)
to
dispatch to get_completion_signatures(sndr)
as a last
resort, per suggestion from Lewis Baker.
Add encouragement for implementors to use the completion signatures of the sender adaptor algorithms to propagate type errors.
Add a design discussion
about the decision to infer that types returned from
get_completion_signatures
represent errors if they are not
specializations of completion_signatures<>
.
R1:
Change the specification of
transform_completion_signatures
to propagate types that are
not specialization of the completion_signatures<>
class template. This makes it easier to use an algorithm's completion
signatures to communicate type errors from child senders.
For the customization points let_value
,
let_error
, and let_stopped
, mandate that the
callable's possible return types all satisfy
sender
.
Change Requires: to Mandates: for algorithms that eagerly connect senders.
R0:
Type-checking a sender expression involves computing its completion signatures. In the general case, a sender's completion signatures may depend on the receiver's execution environment. For example, the sender:
(get_stop_token) read_env
... when connected to a receiver rcvr
and started, will
fetch the stop token from the receiver's environment and then pass it
back to the receiver, as follows:
auto st = get_stop_token(get_env(rcvr));
(move(rcvr), move(st)); set_value
Without an execution environment, the sender
read_env(get_stop_token)
doesn't know how it will
complete.
The type of the environment is known rather late, when the sender is connected to a receiver. This is often far from where the sender expression was constructed. If there are type errors in a sender expression, those errors will be diagnosed far from where the error was made, which makes it harder to know the source of the problem.
It would be far preferable to issue diagnostics while constructing the sender rather than waiting until it is connected to a receiver.
The majority of senders have completions that do not depend on the
receiver's environment. Consider just(42)
-- it will
complete with the integer 42
no matter what receiver it is
connected to. If a so-called "non-dependent" sender advertised itself as
such, then sender algorithms could eagerly type-check the non-dependent
senders they are passed, giving immediate feedback to the developer.
For example, this expression should be immediately rejected:
(42) | then([](int* p) { return *p; }) just
The then
algorithm can reject just(42)
and
the above lambda because the arguments don't match: an integer cannot be
passed to a function expecting an int*
. The
then
algorithm can do that type-checking only when it knows
the input sender is non-dependent. It couldn't, for example, do any
type-checking if the input sender were
read_env(get_stop_token)
instead of
just(42)
.
And in fact, some senders do advertise themselves as non-dependent, although the sender algorithms in ([exec]) do not currently do anything with that extra information. A sender can declare its completions signatures with a nested type alias, as follows:
template <class T>
struct just_sender {
;
T value
using completion_signatures =
std::execution::completion_signatures<
std::execution::set_value_t(T)
>;
// ...
};
Senders whose completions depend on the execution environment cannot
declare their completion signatures this way. Instead, they must define
a get_completion_signatures
customization that takes the
environment as an argument.
We can use this extra bit of information to define a
non_dependent_sender
concept as follows:
template <class Sndr>
concept non_dependent_sender =
<Sndr> &&
senderrequires {
typename remove_reference_t<Sndr>::completion_signatures;
};
A sender algorithm can use this concept to conditionally dispatch to code that does eager type-checking.
The author suggests that this notion of non-dependent senders be
given fuller treatment in std::execution
. Conditionally
defining the nested typedef in generic sender adaptors -- which may
adapt either dependent or non-dependent senders -- is awkward and
verbose. We suggest instead to support calling
get_completion_signatures
either with or without
an execution environment. This makes it easier for authors of sender
adaptors to preserve the "non-dependent" property of the senders it
wraps.
We suggest that a similar change be made to the
completion_signatures_of_t
alias template. When
instantiated with only a sender type, it should compute the
non-dependent completion signatures, or be ill-formed.
Consider the following code, which contains a type error:
auto work = just(42)
| then([](int* p) { // <<< ERROR here
//...
});
The table below shows the result of compiling this code both before the proposed change and after:
Before |
After |
---|---|
no error |
error: static_assert failed due to requirement '_is_completion_signatures< ustdex::ERROR<ustdex::WHERE (ustdex::IN_ALGORITHM, ustdex::then_t), ustdex ::WHAT (ustdex::FUNCTION_IS_NOT_CALLABLE), ustdex::WITH_FUNCTION ((lambda at hello.cpp:57:18)), ustdex::WITH_ARGUMENTS (int)>>' static_assert(_is_completion_signatures<_completions>); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
This error was generated with with µstdex library and Clang-13.
The addition of support for a customization of
get_completion_signatures
that does not take an environment
obviates the need to support the use of a nested
::completion_signatures
alias. In a class, this:
auto get_completion_signatures() ->
std::execution::completion_signatures<
std::execution::set_value_t(T)
>;
... works just as well as this:
using completion_signatures =
std::execution::completion_signatures<
std::execution::set_value_t(T)
>;
Without a doubt, we could simplify the design by dropping support for
the latter. This paper suggests retaining it, though. For something like
the just_sender
, providing type metadata with an alias is
more idiomatic and less surprising, in the author's opinion, than
defining a function and putting the metadata in the return type. That is
the case for keeping the
typename Sndr::completion_signatures
form.
The case for adding the sndr.get_completion_signatures()
form is that it makes it simpler for sender adaptors such as
then_sender
to preserve the "non-dependent" property of the
senders it adapts. For instance, one could define
then_sender
like:
template <class Sndr, class Fun>
struct then_sender {
sndr_;
Sndr fun_;
Fun
template <class... Env>
auto get_completion_signatures(Env&&... env) const
-> some-computed-type;
//....
};
... and with this one member function support both dependent and non-dependent senders while preserving the "non-dependent-ness" of the adapted sender.
This proposed wording is based on the current working draft.
Change [async.ops]/13 as follows:
- A completion signature is a function type that describes a completion operation. An asychronous operation has a finite set of possible completion signatures corresponding to the completion operations that the asynchronous operation potentially evaluates ([basic.def.odr]). For a completion function
set
, receiverrcvr
, and pack of argumentsargs
, letc
be the completion operationset(rcvr, args...)
, and letF
be the function typedecltype(auto(set))(decltype((args))...)
. A completion signatureSig
is associated withc
if and only ifMATCHING-SIG(Sig, F)
istrue
([exec.general]). Together, a sender type and an environment typeEnv
determine the set of completion signatures of an asynchronous operation that results from connecting the sender with a receiver that has an environment of typeEnv
. The type of the receiver does not affect an asychronous operation’s completion signatures, only the type of the receiver’s environment. A sender type whose completion signatures are knowable independent of an execution environment is known as a non-dependent sender.
Change [exec.syn] as follows:
... template<class Sndr, class... Env= env<>> concept sender_in = see below; ... template<class Sndr, class... Env= env<>> requires sender_in<Sndr, Env...> using completion_signatures_of_t = call-result-t<get_completion_signatures_t, Sndr, Env...>; ... template<class Sndr, class... Env> using single-sender-value-type = see below; // exposition only template<class Sndr, class... Env> concept single-sender = see below; // exposition only ...
The exposition-only type
variant-or-empty<Ts...>
is defined as follows ... as beforeFor types
Sndr
and packEnv
, letCS
becompletion_signatures_of_t<Sndr, Env...>
. Thensingle-sender-value-type<Sndr, Env...>
is ill-formed ifCS
is ill-formed or ifsizeof...(Env) > 1
istrue
; otherwise, it is an alias for:
gather-signatures<set_value_t, CS, decay_t, type_identity_t> if that type is well-formed,value_types_of_t<Sndr, Env
Otherwise,
void
ifis
value_types_of_t<Sndr, Envgather-signatures<set_value_t, CS, tuple, variant>variant<tuple<>>
orvariant<>
,Otherwise,
if that type is well-formed,
value_types_of_t<Sndr, Envgather-signatures<set_value_t, CS, decayed-tuple, type_identity_t>Otherwise,
single-sender-value-type<Sndr, Env...>
is ill-formed.The exposition-only concept
single-sender
is defined as follows:namespace std::execution { template<class Sndr, class... Env> concept single-sender = sender_in<Sndr, Env...> && requires { typename single-sender-value-type<Sndr, Env...>; }; }
Change [exec.snd.general] para 1 as follows:
Subclauses [exec.factories] and [exec.adapt] define customizable algorithms that return senders. 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 letSndr
bedecltype((sndr))
. Letrcvr
be a receiver of typeRcvr
with associated environmentenv
of typeEnv
such thatsender_to<Sndr, Rcvr>
istrue
. For the default implementation of the algorithm that producedsndr
, connectingsndr
torcvr
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 torcvr
. LetSigs
be a pack of completion signatures corresponding to this set of completion operations. Then, and letCS
be the type of the expressionget_completion_signatures(sndr, env)
. ThenCS
is a specialization of the class templatecompletion_signatures
([exec.util.cmplsig]), the set of whose template arguments isSigs
. If none of the types inSigs
are dependent on the typeEnv
, then the expressionget_completion_signatures(sndr)
is well-formed and its type isCS
. If a user-provided implementation of the algorithm that producedsndr
is selected instead of the default: Reformatted into a list.
Any completion signature that is in the set of types denoted by
completion_signatures_of_t<Sndr, Env>
and that is not part ofSigs
shall correspond to error or stopped completion operations, unless otherwise specified.If none of the types in
Sigs
are dependent on the typeEnv
, thencompletion_signatures_of_t<Sndr>
andcompletion_signatures_of_t<Sndr, Env>
shall denote the same type.
In [exec.snd.expos] para 24, change the
definition of the exposition-only templates
completion-signatures-for
and
basic-sender
as follows:
template<class Sndr, class... Env> using completion-signatures-for = see below; // exposition only template<class Tag, class Data, class... Child> struct basic-sender : product-type<Tag, Data, Child...> { // exposition only using sender_concept = sender_t; using indices-for = index_sequence_for<Child...>; // exposition only 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 {}; } };
Change [exec.snd.expos] para 39 as follows (this includes the proposed resolution of cplusplus/sender-receiver#307):
- For a subexpression
sndr
letSndr
bedecltype((sndr))
. Letrcvr
be a receiver with an associated environment of typeEnv
such thatsender_in<Sndr, Env>
istrue
.completion-signatures-for<Sndr, Env>
denotes a specialization ofcompletion_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 connectingsndr
andrcvr
. Whensender_in<Sndr, Env>
isfalse
, the type denoted bycompletion-signatures-for<Sndr, Env>
, if any, is not a specialization ofcompletion_signatures
.
Recommended practice: Whensender_in<Sndr, Env>
isfalse
, implementations are encouraged to use the type denoted bycompletion-signatures-for<Sndr, Env>
to communicate to users why.
- Let
Sndr
be a (possiblyconst
-qualified) specializationbasic-sender
or an lvalue reference of such, letRcvr
be the type of a receiver with an associated environment of typeEnv
. If the typebasic-operation<Sndr, Rcvr>
is well-formed, letop
be an lvalue subexpression of that type. Thencompletion-signatures-for<Sndr, Env>
denotes a specialization ofcompletion_signatures
, the set of whose template arguments corresponds to the set of completion operations that are potentially evaluated ([basic.def.odr]) as a result of evaluatingop.start()
. Otherwise,completion-signatures-for<Sndr, Env>
is ill-formed. Ifcompletion-signatures-for<Sndr, Env>
is well-formed and its type is not dependent upon the typeEnv
,completion-signatures-for<Sndr>
is well-formed and denotes the same type; otherwise,completion-signatures-for<Sndr>
is ill-formed.
Change the sender_in
concept in
[exec.snd.concepts] para 1 as follows:
template<class Sndr, class... Env= env<>> concept sender_in = sender<Sndr> && (sizeof...(Env) <= 1) (queryable<Env> &&...) && requires (Sndr&& sndr, Env&&... env) { { get_completion_signatures(std::forward<Sndr>(sndr), std::forward<Env>(env)...) } -> valid-completion-signatures; };
This subtly changes the meaning of
sender_in<Sndr>
. Before the change, it tests whether
a type is a sender when used specifically with the environment
env<>
. After the change, it tests whether a type is a
non-dependent sender. This is a stronger assertion to make about the
type; it says that this type is a sender regardless of the
environment. One can still get the old behavior with
sender_in<Sndr, env<>>
.
Change [exec.snd.concepts] para 4 as follows (so
that the exposition-only sender-of
concept tests
for sender-ness with no environment as opposed to the empty environment,
env<>
):
- The exposition-only concepts
sender-of
andsender-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...); // exposition onlytemplate<class Sndr, class Env, class... Values> concept sender-in-of = sender_in<Sndr, Env> && MATCHING-SIG( // see [exec.general] set_value_t(Values...), value_types_of_t<Sndr, Env, value-signature, type_identity_t>); template<class Sndr, class... Values> concept sender-of = sender-in-of<Sndr, env<>, Values...>;template<class Sndr, class SetValue, class... Env> concept sender-in-of-impl = // exposition only sender_in<Sndr, Env...> && MATCHING-SIG(SetValue, // see [exec.general] gather-signatures<set_value_t, // see [exec.util.cmplsig] completion_signatures_of_t<Sndr, Env...>, value-signature, type_identity_t>); template<class Sndr, class Env, class... Values> concept sender-in-of = // exposition only sender-in-of-impl<Sndr, set_value_t(Values...), Env>; template<class Sndr, class... Values> concept sender-of = // exposition only sender-in-of-impl<Sndr, set_value_t(Values...)>;}
Change [exec.awaitables] p 1-4 as follows:
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
, letGET-AWAITER(c, p)
be expression-equivalent to the series of transformations and conversions applied toc
as the operand of an await-expression in a coroutine, resulting in lvaluee
as described by [expr.await], wherep
is an lvalue referring to the coroutine’s promise, which has typePromise
.[Note 1: This includes the invocation of the promise type’s
await_transform
member if any, the invocation of theoperator co_await
picked by overload resolution if any, and any necessary implicit conversions and materializations. -- end note]Let
GET-AWAITER(c)
be expression-equivalent toGET-AWAITER(c, q)
whereq
is an lvalue of an unspecified empty class typenone-such
that lacks anawait_transform
member, and wherecoroutine_handle<none-such>
behaves ascoroutine_handle<void>
.Let
is-awaitable
be the following exposition-only concept:template<class T> concept await-suspend-result = see below; template<class A, class... Promise> concept is-awaiter = // exposition only 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>
istrue
if and only if one of the following istrue
:
T
isvoid
, orT
isbool
, orT
is a specialization ofcoroutine_handle
.For a subexpression
c
such thatdecltype((c))
is typeC
, and an lvaluep
of typePromise
,await-result-type<C, Promise>
denotes the typedecltype(GET-AWAITER(c, p).await_resume())
andawait-result-type<C>
denotes the typedecltype(GET-AWAITER(c).await_resume())
.
Change [exec.getcomplsigs] as follows:
get_completion_signatures
is a customization point object. Letsndr
be an expression such thatdecltype((sndr))
isSndr
, and letenv
bean expression such thata pack of zero or one expression.decltype((env))
isEnv
LetIfsizeof...(env) == 0
istrue
, letnew_sndr
besndr
; otherwise, letnew_sndr
be the expressiontransform_sender(decltype(get-domain-late(sndr, env...)){}, sndr, env...)
., and letLetNewSndr
bedecltype((new_sndr))
. Thenget_completion_signatures(sndr, env...)
is expression-equivalent to(void(sndr), void(env)..., CS())
except thatvoid(sndr)
andvoid(env)...
are indeterminately sequenced, whereCS
is:
decltype(new_sndr.get_completion_signatures(env
if that type is well-formed,...
))Otherwise, if
sizeof...(env) == 1
istrue
, thendecltype(new_sndr.get_completion_signatures())
if that expression is well-formed,Otherwise,
remove_cvref_t<NewSndr>::completion_signatures
if that type is well-formed,Otherwise, if
is-awaitable<NewSndr, env-promise<
isEnvdecltype((env))>...>true
, then:completion_signatures< SET-VALUE-SIG(await-result-type<NewSndr, env-promise<Envdecltype((env))>...>), // see [exec.snd.concepts] set_error_t(exception_ptr), set_stopped_t()>
- Otherwise,
CS
is ill-formed.
If
get_completion_signatures(sndr)
is well-formed and its type denotes a specialization of thecompletion_signatures
class template, thenSndr
is a non-dependent sender type ([async.ops]).Given a type
Env
, ifcompletion_signatures_of_t<Sndr>
andcompletion_signatures_of_t<Sndr, Env>
are both well-formed, they shall denote the same type.
- Let
rcvr
be an rvalue whose typeRcvr
...as before
Change [exec.adapt.general] as follows:
- (3.4)When a parent sender is connected to a receiver
rcvr
, any receiver used to connect a child sender has an associated environment equal toFWD-ENV(get_env(rcvr))
.- (3.5)An adaptor whose child senders are all non-dependent ([async.ops]) is itself non-dependent.
(3.6)These requirements apply to any function that is selected by the implementation of the sender adaptor.
- (3.7)Recommended practice: Implementors are encouraged to use the completion signatures of the adaptors to communicate type errors to users and to propagate any such type errors from child senders.
Change [exec.then] as follows:
- The names
then
,upon_error
, andupon_stopped
denote pipeable sender adaptor objects. Forthen
,upon_error
, andupon_stopped
, letset-cpo
beset_value
,set_error
, andset_stopped
respectively. Let the expressionthen-cpo
be one ofthen
,upon_error
, orupon_stopped
. For subexpressionssndr
andf
, ifdecltype((sndr))
does not satisfysender
, ordecltype((f))
does not satisfymovable-value
,then-cpo(sndr, f)
is ill-formed.
- Otherwise, let
invoke-result
be an alias template such thatinvoke-result<Ts...>
denotes the typeinvoke_result_t<F, Ts...>
whereF
is the decayed type off
. The expressionthen-cpo(sndr, f)
mandates that eithersender_in<Sndr>
isfalse
or the typegather-signatures<decayed-typeof<set-cpo>, completion_signatures_of_t<Sndr>, invoke-result, type-list>
is well-formed.
Otherwise, theThe expressionthen-cpo(sndr, f)
is expression-equivalent to:except thattransform_sender(get-domain-early(sndr), make-sender(then-cpo, f, sndr))sndr
is evaluated only once.
ForThe exposition-only class templatethen
,upon_error
, andupon_stopped
, letset-cpo
beset_value
,set_error
, andset_stopped
respectively.impls-for
([exec.snd.general]) is specialized forthen-cpo
as follows: ...as beforeChange [exec.let] by inserting a new paragraph between (3) and (4) as follows:
- Let
invoke-result
be an alias template such thatinvoke-result<Ts...>
denotes the typeinvoke_result_t<F, Ts...>
whereF
is the decayed type off
. The expressionlet-cpo(sndr, f)
mandates that eithersender_in<Sndr>
isfalse
or the typegather-signatures<decayed-typeof<set-cpo>, completion_signatures_of_t<Sndr>, invoke-result, type-list>
is well-formed and that the types in the resulting type list all satisfysender
.
Otherwise, theThe expressionlet-cpo(sndr, f)
is expression-equivalent to:except thattransform_sender(get-domain-early(sndr), make-sender(let-cpo, f, sndr))sndr
is evaluated only once.Change [exec.bulk] by inserting a new paragraph between (1) and (2) as follows:
- Let
invoke-result
be an alias template such thatinvoke-result<Ts...>
denotes the typeinvoke_result_t<F, Shape, Ts...>
whereF
is the decayed type off
. The expressionbulk(sndr, f)
mandates that eithersender_in<Sndr>
isfalse
or the typegather-signatures<set_value_t, completion_signatures_of_t<Sndr>, invoke-result, type-list>
is well-formed.
Otherwise, theThe expressionbulk(sndr, shape, f)
is expression-equivalent to:except thattransform_sender(get-domain-early(sndr), make-sender(bulk, product-type{shape f}, sndr))sndr
is evaluated only once.Change [exec.split] as follows:
The name
split
denotes a pipeable sender adaptor object. For a subexpressionsndr
, letSndr
bedecltype((sndr))
.IfThe expressionsplit(sndr)
mandates thatsender_in<Sndr, split-env>
istrue
.false
,split(sndr)
is ill-formed
Otherwise, theThe expressionsplit(sndr)
is expression-equivalent to:except thattransform_sender(get-domain-early(sndr), make-sender(split, {}, sndr))sndr
is evaluated only once.[Note 1: The default implementation of
transform_sender
will have the effect of connecting the sender to a receiver. It will return a sender with a different tag type. -- end note]Change [exec.stopped.opt] as follows:
The name
stopped_as_optional
denotes a pipeable sender adaptor object. For a subexpressionsndr
, letSndr
bedecltype((sndr))
. The expressionstopped_as_optional(sndr)
mandates that!sender_in<Sndr> || single-sender<Sndr>
istrue
. The expressionstopped_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.Let
sndr
andenv
be subexpressions such thatSndr
isdecltype((sndr))
andEnv
isdecltype((env))
. Ifsender-for<Sndr, stopped_as_optional_t>
isfalse
, or if the typethen the expressionsingle-sender-value-type<Sndr, Env>
is ill-formed orvoid
,stopped_as_optional.transform_sender(sndr, env)
is ill-formed; otherwise, that expression mandates that the typesingle-sender-value-type<Sndr, Env>
is well-formed and notvoid
, andotherwise, itis equivalent to: ... as beforeChange [exec.sync.wait] as follows:
The name
this_thread::sync_wait
denotes a customization point object. For a subexpressionsndr
, letSndr
bedecltype((sndr))
.IfThe expressionsender_in<Sndr, sync-wait-env>
isfalse
, the expressionthis_thread::sync_wait(sndr)
is ill-formed. Otherwise, itthis_thread::sync_wait(sndr)
is expression-equivalent to the following, except thatsndr
is evaluated only once:apply_sender(get-domain-early(sndr), sync_wait, sndr)Mandates:
- (4.1)
sender_in<Sndr, sync-wait-env>
istrue
.- (4.2) The type
sync-wait-result-type<Sndr>
is well-formed.- (4.3)
same_as<decltype(e), sync-wait-result-type<Sndr>>
istrue
, wheree
is theapply_sender
expression above....as before
Change [exec.sync.wait.var] as follows:
The name
this_thread::sync_wait_with_variant
denotes a customization point object. For a subexpressionsndr
, letSndr
bedecltype(into_variant(sndr))
.IfThe expressionsender_in<Sndr, sync-wait-env>
isfalse
, the expressionthis_thread::sync_wait(sndr)
is ill-formed. Otherwise, itthis_thread::sync_wait_with_variant(sndr)
is expression-equivalent to the following, except thatsndr
is evaluated only once:apply_sender(get-domain-early(sndr), sync_wait_with_variant, sndr)Mandates:
- (1.1)
sender_in<Sndr, sync-wait-env>
istrue
.- (1.2) The type
sync-wait-with-variant-result-type<Sndr>
is well-formed.- (1.3)
same_as<decltype(e), sync-wait-with-variant-result-type<Sndr>>
istrue
, wheree
is theapply_sender
expression above.
IfThe expressioncallable<sync_wait_t, Sndr>
isfalse
, the expressionsync_wait_with_variant.apply_sender(sndr)
is ill-formed. Otherwise, itsync_wait_with_variant.apply_sender(sndr)
is equivalent to ...as beforeChange [exec.run.loop.types] para 5 as follows:
run-loop-sender
is an exposition-only type that satisfiessender
.For any typeEnv
,completion_signatures_of_t< run-loop-sender
is, Env>completion_signatures<set_value_t(), set_error_t(exception_ptr), set_stopped_t()>
.Acknowlegments
We owe our thanks to Ville Voutilainen who first noticed that most sender expressions could be type-checked eagerly but are not by P2300R8.