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 connects 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:
read_env(get_stop_token)... 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));
set_value(move(rcvr), move(st));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:
just(42) | then([](int* p) { return *p; })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 =
sender<Sndr> &&
requires {
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, letcbe the completion operationset(rcvr, args...), and letFbe the function typedecltype(auto(set))(decltype((args))...). A completion signatureSigis associated withcif and only ifMATCHING-SIG(Sig, F)istrue([exec.general]). Together, a sender type and an environment typeEnvdetermine 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
Sndrand packEnv, letCSbecompletion_signatures_of_t<Sndr, Env...>. Thensingle-sender-value-type<Sndr, Env...>is ill-formed ifCSis ill-formed or ifsizeof...(Env) > 1istrue; 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, EnvOtherwise,
voidifisvalue_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-senderis 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
sndrbe the result of an invocation of such an algorithm or an object equal to the result ([concepts.equality]), and letSndrbedecltype((sndr)). Letrcvrbe a receiver of typeRcvrwith associated environmentenvof typeEnvsuch thatsender_to<Sndr, Rcvr>istrue. For the default implementation of the algorithm that producedsndr, connectingsndrtorcvrand 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. LetSigsbe a pack of completion signatures corresponding to this set of completion operations. Then, and letCSbe the type of the expressionget_completion_signatures(sndr, env). ThenCSis a specialization of the class templatecompletion_signatures([exec.util.cmplsig]), the set of whose template arguments isSigs. If none of the types inSigsare 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 producedsndris 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 ofSigsshall correspond to error or stopped completion operations, unless otherwise specified.If none of the types in
Sigsare 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
sndrletSndrbedecltype((sndr)). Letrcvrbe a receiver with an associated environment of typeEnvsuch 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 connectingsndrandrcvr. 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
Sndrbe a (possiblyconst-qualified) specializationbasic-senderor an lvalue reference of such, letRcvrbe the type of a receiver with an associated environment of typeEnv. If the typebasic-operation<Sndr, Rcvr>is well-formed, letopbe 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-ofandsender-in-ofdefine 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_awaitexpression within a given context.For a subexpression
c, letGET-AWAITER(c, p)be expression-equivalent to the series of transformations and conversions applied tocas the operand of an await-expression in a coroutine, resulting in lvalueeas described by [expr.await], wherepis an lvalue referring to the coroutine’s promise, which has typePromise.[Note 1: This includes the invocation of the promise type’s
await_transformmember if any, the invocation of theoperator co_awaitpicked 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)whereqis an lvalue of an unspecified empty class typenone-suchthat lacks anawait_transformmember, and wherecoroutine_handle<none-such>behaves ascoroutine_handle<void>.Let
is-awaitablebe 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>istrueif and only if one of the following istrue:
Tisvoid, orTisbool, orTis a specialization ofcoroutine_handle.For a subexpression
csuch thatdecltype((c))is typeC, and an lvaluepof 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_signaturesis a customization point object. Letsndrbe an expression such thatdecltype((sndr))isSndr, and letenvbean expression such thata pack of zero or one expression.decltype((env))isEnvLetIfsizeof...(env) == 0istrue, letnew_sndrbesndr; otherwise, letnew_sndrbe the expressiontransform_sender(decltype(get-domain-late(sndr, env...)){}, sndr, env...)., and letLetNewSndrbedecltype((new_sndr)). Thenget_completion_signatures(sndr, env...)is expression-equivalent to(void(sndr), void(env)..., CS())except thatvoid(sndr)andvoid(env)...are indeterminately sequenced, whereCSis:
decltype(new_sndr.get_completion_signatures(envif that type is well-formed,...))Otherwise, if
sizeof...(env) == 1istrue, thendecltype(new_sndr.get_completion_signatures())if that expression is well-formed,Otherwise,
remove_cvref_t<NewSndr>::completion_signaturesif 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,
CSis ill-formed.
If
get_completion_signatures(sndr)is well-formed and its type denotes a specialization of thecompletion_signaturesclass template, thenSndris 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
rcvrbe 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_stoppeddenote pipeable sender adaptor objects. Forthen,upon_error, andupon_stopped, letset-cpobeset_value,set_error, andset_stoppedrespectively. Let the expressionthen-cpobe one ofthen,upon_error, orupon_stopped. For subexpressionssndrandf, ifdecltype((sndr))does not satisfysender, ordecltype((f))does not satisfymovable-value,then-cpo(sndr, f)is ill-formed.
- Otherwise, let
invoke-resultbe an alias template such thatinvoke-result<Ts...>denotes the typeinvoke_result_t<F, Ts...>whereFis the decayed type off. The expressionthen-cpo(sndr, f)mandates that eithersender_in<Sndr>isfalseor 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))sndris evaluated only once.
ForThe exposition-only class templatethen,upon_error, andupon_stopped, letset-cpobeset_value,set_error, andset_stoppedrespectively.impls-for([exec.snd.general]) is specialized forthen-cpoas follows: ...as beforeChange [exec.let] by inserting a new paragraph between (3) and (4) as follows:
- Let
invoke-resultbe an alias template such thatinvoke-result<Ts...>denotes the typeinvoke_result_t<F, Ts...>whereFis the decayed type off. The expressionlet-cpo(sndr, f)mandates that eithersender_in<Sndr>isfalseor 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))sndris evaluated only once.Change [exec.bulk] by inserting a new paragraph between (1) and (2) as follows:
- Let
invoke-resultbe an alias template such thatinvoke-result<Ts...>denotes the typeinvoke_result_t<F, Shape, Ts...>whereFis the decayed type off. The expressionbulk(sndr, f)mandates that eithersender_in<Sndr>isfalseor 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))sndris evaluated only once.Change [exec.split] as follows:
The name
splitdenotes a pipeable sender adaptor object. For a subexpressionsndr, letSndrbedecltype((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))sndris evaluated only once.[Note 1: The default implementation of
transform_senderwill 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_optionaldenotes a pipeable sender adaptor object. For a subexpressionsndr, letSndrbedecltype((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
sndris only evaluated once.Let
sndrandenvbe subexpressions such thatSndrisdecltype((sndr))andEnvisdecltype((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_waitdenotes a customization point object. For a subexpressionsndr, letSndrbedecltype((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 thatsndris 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, whereeis theapply_senderexpression above....as before
Change [exec.sync.wait.var] as follows:
The name
this_thread::sync_wait_with_variantdenotes a customization point object. For a subexpressionsndr, letSndrbedecltype(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 thatsndris 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, whereeis theapply_senderexpression 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-senderis an exposition-only type that satisfiessender.For any typeEnv,completion_signatures_of_t< run-loop-senderis, 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.