Document #: | P3557R2 |
Date: | 2025-05-16 |
Project: | Programming Language C++ |
Audience: |
LWG Library Working Group |
Reply-to: |
Eric Niebler <eric.niebler@gmail.com> |
“Only the exceptional paths bring exceptional glories!”
— Mehmet Murat Ildan
The hardest part of writing a sender algorithm is often the computation of its completion signatures, an intricate meta-programming task. Using sender algorithms incorrectly leads to large, incomprehensible errors deep within the completion-signatures meta-program. What is needed is a way to propagate type errors automatically to the API boundary where they can be reported concisely, much the way exceptions do for runtime errors.
Support for exceptions during constant-evaluation was recently accepted into the Working Draft for C++26. We can take advantage of this powerful new feature to easily propagate type errors during the computation of a sender’s completion signatures. This significantly improves the diagnostics users are likely to encounter while also simplifying the job of writing new sender algorithms.
This paper proposes the following changes to the working draft with the addition of [P3164R4]. Subsequent sections will address the motivation and the designs in detail.
std::execution::get_completion_signatures
from a customization point object that accepts a sender and (optionally)
an environment to a consteval
function template that takes no arguments, as follows:
Before
|
After
|
---|---|
|
|
get_completion_signatures
from a
member function that accepts the cv-qualified sender object and
an optional environment object to a
static constexpr
function
template that take the sender and environment types as template
parameters.
Before
|
After
|
---|---|
|
|
sender_in<Sender, Env...>
concept to test that get_completion_signatures<Sndr, Env...>()
is a constant expression.
Before
|
After
|
---|---|
|
|
In the exposition-only
basic-sender
class
template, specify under what conditions its
get_completion_signatures
static
member function is ill-formed when called without an
Env
template parameter (see
proposed wording for details).
Add a dependent_sender
concept that is modeled by sender types that do not know how they will
complete independent of their execution environment.
Remove the
transform_completion_signatures
alias template (to be later replaced with a
consteval
function that does the
same job and that throws on error).
Move the nice-to-haves into a separate paper (TBD).
Drop support for specifying a sender’s completion signatures with a nested type alias.
Promote the exposition-only
dependent-sender-error
class to std::execution::dependent_sender_error
.
Change
and all its customizations from
basic-sender
::check-types
constexpr
to
consteval
.
Merge the proposed wording of [P3164R4] into this paper’s proposed wording for ease of wording review.
Since R0, a significant fraction of C++26’s
std::execution
has been
implemented with the design changes proposed by this paper. Several bugs
in R0 have been found and fixed as a result.
In addition, this paper exposed several bugs in the Working Draft for C++26. As those bugs relate to the computation of completion signatures, R1 integrates the proposed fixes for those bugs.
std::execution::get_completion_signatures
which was the victim of an incomplete last-minute edit.get_completion_signatures
.transform_completion_signatures
to take a transform function for the stopped completion signature
instead of a
completion_signatures
object.impls-for
<Tag>::check-types
customizations.basic-sender
<Tag>::get_completion_signatures
.std::execution::connect
.stopped_as_optional.transform_sender(sndr, env)
.let_*
,
schedule_from
,
into_variant
,
split
, and
when_all
algorithms when the
prececessor’s result datums cannot be copied into intermediate
storage.eptr_completion_if
make_completion_signatures
transform_completion_signatures
(a constexpr
function
version)invalid_completion_signature
stopped_as_optional
tests
constraints satisfaction of self instead of child”.FWD-ENV
as they
should”.as-sndr2(Sig)
in [exec.let] is incomplete”.let_[*].transform_env
is
specified in terms of the let_*
sender itself instead of its child”.This paper exists principly to improve the experience of users who make type errors in their sender expressions by leveraging exceptions during constant- evaluation. It is a follow-on of [P3164R4], which defines a category of “non-dependent” senders that can and must be type-checked early.
Senders have a construction phase and a subsequent connection phase. Prior to P3164, all type-checking of senders happened at the connection phase (when a sender is connected to a receiver). P3164 mandates that the sender algorithms type-check non-dependent senders, moving the diagnostic closer to the source of the error.
This paper addresses the quality of those diagnostics, as well as the diagnostics users encounter when a dependent sender fails type-checking at connection time.
Senders are expression trees, and type errors can happen deep within their structure. If programmed naively, ill-formed senders would generate megabytes of incomprehensible diagnostics. The challenge is to report type errors concisely and comprehensibly, at the right level of abstraction.
Doing this requires propagating domain-specific descriptions of type errors out of the completion signatures meta-program so they can be reported concisely. Such error detection and propagation is very cumbersome in template meta-programming.
The C++ solution to error propagation is exceptions. With the
adoption of [P3068R6], C++26 has
gained the ability to throw and catch exceptions during
constant-evaluation. If we express the computation of completion
signatures as a constexpr
meta-program, we can use exceptions to propagate type errors. This
greatly improves diagnostics and even simplifies the code that computes
completion signatures.
This paper proposes changes to
std::execution
that make the
computation of a sender’s completion signatures an evaluation of a
constexpr
function. It also
specifies the conditions under which the computation is to exit with an
exception.
How good are std::execution
’s
diagnostics with constexpr
exceptions? Let’s consider a user that makes a simple mistake in their
sender expression:
using stdex = std::execution; auto sndr = stdex::just(42) | stdex::then([]() { /*…*/ });
This is an error because the nullary lambda cannot be called with the
integer that just(42)
completes
with.
With constexpr
exceptions,
the resulting diagnostic is blissfully direct:
constexpr
exceptions:<source>:658:3: error: call to immediate function 'operator|<_basic_sender<just_t, _tupl<nullptr, int>>>' is not a constant expression 2212 | auto sndr = just(42) | then([](){}); | ^ <source>:658:3: note: 'operator|<_basic_sender<just_t, _tupl<nullptr, int>>>' is an immediate function because its body contains a call to an immediate function '_make_sender<then_t, (lambda at <source>:2212: 31), _basic_sender<just_t, _tupl<nullptr, int>>>' and that call is not a constant expression 1912 | return transform_sender(dom, _make_sender(Algorithm, std::move(_self.data_), sndr)); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:658:3: note: unhandled exception of type '_sender_type_check_failure<const char *, IN_ALGORITHM< then>, WITH_FUNCTION ((lambda at <source>:2212:31)), WITH_ARGUMENTS (int)>' with content {{{}}, &"The fun ction passed to std::execution::then is not callable with the values sent by the predecessor sender."[0]} thrown from here 876 | throw _sender_type_check_failure<Values...[0], What...>(values...); | ^
The above is the complete diagnostic, regardless of how deeply nested the type error is.
In contrast, the following is the diagnostic we get without
constexpr
exceptions. Notice
that the backtrace is truncated; the actual error is considerably
longer.
constexpr
exceptions:<source>:1912:36: error: call to immediate function 'operator|<_basic_sender<just_t, _tupl<nullptr, int>>>' is not a constant expression 2212 | auto sndr = just(42) | then([](){}); | ^ <source>:1912:36: note: 'operator|<_basic_sender<just_t, _tupl<nullptr, int>>>' is an immediate function because its body cont ains a call to an immediate function '_make_sender<then_t, (lambda at <source>:2212:31), _basic_sender<just_t, _tupl<nullptr, int>>>' and that call is not a constant expression 1912 | return transform_sender(dom, _make_sender(Algorithm, std::move(_self.data_), sndr)); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:876:5: note: subexpression not valid in a constant expression 876 | throw _sender_type_check_failure<Values...[0], What...>(values...); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:1990:16: note: in call to 'invalid_completion_signature<IN_ALGORITHM<then>, WITH_FUNCTION ((lambda at <source>:2212:3 1)), WITH_ARGUMENTS (int), const char *>(&"The function passed to std::execution::then is not callable with the values sent by the predecessor sender."[0])' 1990 | return invalid_completion_signature<IN_ALGORITHM<then>, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1991 | struct WITH_FUNCTION(Fn), | ~~~~~~~~~~~~~~~~~~~~~~~~~ 1992 | struct WITH_ARGUMENTS(As...)>( | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1993 | "The function passed to std::execution::then is not callable with the" | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1994 | " values sent by the predecessor sender."); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:885:10: note: in call to 'fn.operator()<int>()' 885 | return fn.template operator()<As...>(); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:897:12: note: in call to '_transform_expr<int, _impls_for<then_t>::(lambda at <source>:1988:5)>(fn..)' 897 | return ::_transform_expr<As...>(fn); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:923:14: note: in call to '_apply_transform<int, _impls_for<then_t>::(lambda at <source>:1988:5)>(fn..)' 923 | return _apply_transform<Ts...>(value_fn); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:930:13: note: in call to 'transform1.operator()<set_value_t, int>(nullptr)' 930 | return (transform1(sigs) +...+ completion_signatures()); | ^~~~~~~~~~~~~~~~ <source>:804:12: note: (skipping 6 calls in backtrace; use -fconstexpr-backtrace-limit=0 to see all) 804 | return fn(_normalized_sig_t<Sigs>()...); | ^ <source>:1206:31: note: in call to 'get_completion_signatures<_basic_sender<then_t, (lambda at <source>:2212:31), _basic_sende r<just_t, _tupl<nullptr, int>>>>()' 1206 | return _CHECKED_COMPLSIGS(_GET_COMPLSIGS(Sndr, Env...)); | ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:1188:3: note: expanded from macro '_GET_COMPLSIGS' 1188 | std::remove_reference_t<Sndr>::template get_completion_signatures<Sndr __VA_OPT__(,) __VA_ARGS__>() | ^ <source>:1190:34: note: expanded from macro '_CHECKED_COMPLSIGS' 1190 | #define _CHECKED_COMPLSIGS(...) (__VA_ARGS__, ::_checked_complsigs<decltype(__VA_ARGS__)>()) | ^~~~~~~~~~~ <source>:1230:10: note: in call to '_get_completion_signatures_helper<_basic_sender<then_t, (lambda at <source>:2212:31), _bas ic_sender<just_t, _tupl<nullptr, int>>>>()' 1230 | return _get_completion_signatures_helper<Sndr>(); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:1872:12: note: in call to 'get_completion_signatures<_basic_sender<then_t, (lambda at <source>:2212:31), _basic_sende r<just_t, _tupl<nullptr, int>>>>()' 1872 | (void) get_completion_signatures<Sender>(); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:1912:36: note: in call to '_make_sender<then_t, (lambda at <source>:2212:31), _basic_sender<just_t, _tupl<nullptr, in t>>>({{}}, {}, {{}, {}, {{42}}})' 1912 | return transform_sender(dom, _make_sender(Algorithm, std::move(_self.data_), sndr)); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:2212:15: note: in call to 'operator|<_basic_sender<just_t, _tupl<nullptr, int>>>({{}, {}, {{42}}}, {{}})' 2212 | auto sndr = just(42) | then([](){}); | ^~~~~~~~~~~~~~~~~~~~~~~
The code that generated these diagnostics can be found here or at https://godbolt.org/z/rPEqWz693
get_completion_signatures
In the Working Draft, a sender’s completion signatures are determined
by the type of the expression std::execution::get_completion_signatures(sndr, env)
(or, after P3164, std::execution::get_completion_signatures(sndr)
for non-dependent senders). Only the type of the expression matters; the
expression itself is never evaluated.
In the design proposed by this paper, the
get_completion_signatures
expression must be constant-evaluated in order use exceptions to report
errors. To make it ammenable to constant evaluation, it must not accept
arguments with runtime values, so the expression is changed to std::execution::get_completion_signatures<Sndr, Env...>()
,
where get_completion_signatures
is a consteval
function.
If an unhandled exception propagates out of
get_completion_signatures
the
program is ill-formed (because
get_completion_signatures
is
consteval
). The diagnostic
displays the type and value of the exception.
std::execution::get_completion_signatures<Sndr, Env...>()
in turn calls remove_reference_t<Sndr>::template get_completion_signatures<Sndr, Env...>()
,
which computes the completion signatures or throws as appropriate, as
shown below:
namespace exec = std::execution; struct void_sender { using sender_concept = exec::sender_t; template <class Self, class... Env> static constexpr auto get_completion_signatures() { return exec::completion_signatures<exec::set_value_t()>(); } /* … more … */ };
To better support the
constexpr
value-oriented
programming style, calls to std::execution::get_completion_signatures
from a constexpr
function are
never ill-formed, and their return type is always a specialization of
the completion_signatures
template. std::execution::get_completion_signatures
reports errors by failing to be a constant expression.
[P3164R4] introduces the concept of
non-dependent senders: senders that have the same completion signatures
regardless of the receiver’s execution environment. For a sender type
DependentSndr
whose completions
do depend on the environment, what should happen when the
sender’s completions are queried without an environment? That is, what
should the semantics be for get_completion_signatures<DependentSndr>()
?
get_completion_signatures<DependentSndr>()
should follow the general rule: it should be well-formed in a
constexpr
function, and it
should have a
completion_signatures
type. That
way, sender adaptors do not need to do anything special when computing
the completions of child senders that are dependent. So get_completion_signatures<DependentSndr>()
should throw.
If get_completion_signatures<Sndr>()
throws for dependent senders, and it also throws for non-dependent
senders that fail to type-check, how then do we distinguish between
valid dependent and invalid non-dependent senders? We can distinguish by
checking the type of the exception.
An example will help. Consider the
read_env(q)
sender, a dependent
sender that sends the result of calling
q
with the receiver’s
environment. It cannot compute its completion signatures without an
environment. The natural way for the
read_env
sender to express that
is to require an Env
parameter
to its customization of
get_completion_signatures
:
namespace exec = std::execution; template <class Query> struct read_env_sender { using sender_concept = exec::sender_t; template <class Self, class Env> // NOTE: Env is not optional! static constexpr auto get_completion_signatures() { if constexpr (!std::invocable<Query, Env>) { throw
exception-type-goes-here
(); } else { using Result = std::invoke_result_t<Query, Env>; return exec::completion_signatures<exec::set_value_t(Result)>(); } } /* … more … */ };
That makes read_env_sender<Q>::get_completion_signatures<Sndr>()
an ill-formed expression, which the
get_completion_signatures
function can detect. In such cases, it would throw an exception of a
special type that it can catch later when distinguishing between
dependent and non-dependent senders.
Since the design has several parts, reading the implementation of
get_completion_signatures
is
probably the easiest way to understand it. The implementation is shown
below with comments describing the parts.
// This macro expands to an invocation of SNDR's get_completion_signatures // customization. #define GET_COMPLSIGS(SNDR, ...) std::remove_reference_t<SNDR>::template \ get_completion_signatures<SNDR __VA_OPT__(,) __VA_ARGS__>() // This macro expands to an evaluation of EXPR, followed by an invocation // of the _checked_complsigs function which validates its type. #define CHECK_COMPLSIGS(EXPR) (EXPR, _check_complsigs<decltype(EXPR)>()) // This helper ensures that the passed type is indeed a specialization of the // completion_signatures class template, and throws if it is not. template <class Completions> consteval auto _check_complsigs() { if constexpr (
valid-completion-signatures
<Completions>) // We got a type that is a specialization of the completion_signatures // class template representing the sender's completions. Return it. return Completions(); else // throw unconditionally but make sure the return type is deduced to // be `completion_signatures<>`. That way, a sender adaptor can ask // a child for its completions and take for granted that the return // type is a specialization of `completion_signatures<>` return (throwunspecified
, completion_signatures()); } template <class Sndr, class... Env> consteval auto _get_completion_signatures_helper() { // The following `if` tests whether GET_COMPLSIGS(Sndr, Env...) // is a well-formed expression. if constexpr (requires { GET_COMPLSIGS(Sndr, Env...); }) { // The GET_COMPLSIGS(Sndr, Env...) expression is well-formed, but it may // throw an exception or otherwise fail to be a constant expression. // By evaluating it, we cause its exception and its non-constexpr-ness to // propagate. Then CHECK_COMPLSIGS ensures that its type is indeed // a specialization of the completion_signatures class template. return CHECK_COMPLSIGS(GET_COMPLSIGS(Sndr, Env...)); } // The following `if` does the same as above, but for GET_COMPLSIGS(Sndr). // A non-dependent sender may announce itself by way of this signature. else if constexpr (requires { GET_COMPLSIGS(Sndr); }) { // Same as above: propagate any exceptions and non-constexpr-ness, and // verify the expression has the right type. return CHECK_COMPLSIGS(GET_COMPLSIGS(Sndr)); } // We can't recognize Sndr as a sender proper, but maybe it is awaitable, // in which case we can adapt it to be a sender. else if constexpr (is-awaitable
<Sndr,env-promise
<Env>...>) { return completion_signatures<SET-VALUE-SIG
(await-result-type
<Sndr,env-promise
<Env>...>), // ([exec.snd.concepts]) (exception_ptr), set_error_t()>(); set_stopped_t} // If none of the above expressions are well-formed, then we don't know // the sender's completions. If we are testing without an environment, then // we assume Sndr is a dependent sender. Throw an exception that // communicates that. else if constexpr (sizeof...(Env) == 0) { return (throw dependent_sender_error(), completion_signatures()); } else { // We cannot compute the completion signatures for this sender and // environment. Give up and throw an exception. return (throwunspecified
, completion_signatures()); } } template <class Sndr> consteval auto get_completion_signatures() -> _valid_completion_signatures auto { // There is no environment, which means we are asking for the sender's non- // dependent completion signatures. If the sender is dependent, this will // exit with a special exception type. return _get_completion_signatures_helper<Sndr>(); } template <class Sndr, class Env> consteval auto get_completion_signatures() -> _valid_completion_signatures auto { // Apply a lazy sender transform if one exists before computing the completion signatures: using Domain = decltype(_get_domain_late(std::declval<Sndr>(), std::declval<Env>())); using NewSndr = decltype(transform_sender(Domain(), std::declval<Sndr>(), std::declval<Env>())); return _get_completion_signatures_helper<NewSndr, Env>(); }
Given this definition of
get_completion_signatures
, we
can implement a dependent_sender
concept as follows:
// Returns true when get_completion_signatures<Sndr>() throws a // dependent_sender_error or a type dereived from it. Returns false when // get_completion_signatures<Sndr>() returns normally (Sndr is non-dependent). template <class Sndr> consteval bool
is-dependent-sender-helper
() try { (void) get_completion_signatures<Sndr>(); return false; } catch (dependent_sender_error&) { return true; } // If get_completion_signatures<Sndr>() throws an exception other than // dependent_sender_error, thenis-dependent-sender-helper
<Sndr>() will // fail to be a constant expression and so 2ill not be a valid non-type // template parameter to bool_constant. Therefore, dependent_sender<Sndr> will // be false. template <class Sndr> concept dependent_sender = <Sndr> && sender<is-dependent-sender-helper
<Sndr>()>::value; bool_constant
After the adoption of [P3164R4], the sender
algorithms are all required to return senders that are either dependent
or else that type-check successfully. This paper proposes adding that
type-checking as a Mandates on the exposition-only
make-sender
function
template that all the algorithms use to construct their return
value.
Users who define their own sender algorithms can use
dependent_sender
and
get_completion_signatures
to
perform early type-checking of their own sender types using a helper
such as the following:
template <class Sndr> constexpr auto _type_check_sender(Sndr sndr) { if constexpr (!dependent_sender<Sndr>) { // This line will fail to compile if Sndr fails its type checking. We // don't want to perform this type checking when Sndr is dependent, though. // Without an environment, the sender doesn't know its completions. (void) get_completion_signatures<Sndr>(); } return sndr; }
Using this helper, a then
algorithm might type-check its returned senders as follows:
inline constexpr struct then_t { template <sender Sndr, class Fn> auto operator()(Sndr sndr, Fn fn) const { return _type_check_sender(_then_sender{std::move(sndr), std::move(fn)}); } } then {};
sender_in
With the above changes, we need to tweak the
sender_in
concept to require
that get_completion_signatures<Sndr, Env...>()
is a constant expression.
The changes to sender_in
relative to [P3164R4] are as
follows:
template <auto>
concept is-constant = true; // exposition only
template<class Sndr, class... Env> concept sender_in = <Sndr> && sender(sizeof...(Env) <= 1) (queryable<Env> &&...) &&is-constant<get_completion_signatures<Sndr, Env...>()>;
requires (Sndr&& sndr, Env&&... env) {
{ get_completion_signatures(std::forward<Sndr>(sndr), std::forward<Env>(env)...) }
-> valid-completion-signatures;
};
basic-sender
The sender algorithms are expressed in terms of the exposition-only
class template
basic-sender
. The
mechanics of computing completion signatures is not specified, however,
so very little change there is needed to implement this proposal.
We do, however, have to say when
is ill-formed. In [P3164R4],
non-dependent senders are dealt with by discussing whether or not a
sender’s potentially-evaluated completion operations are dependent on
the type of the receiver’s environment. In this paper, we make a similar
appeal when specifying whether or not basic-sender
::get_completion_signatures<Sndr>()
is well-formed.basic-sender
::get_completion_signatures<Sndr>()
dependent_sender
Users who write their own sender adaptors will also want to perform early type-checking of senders that are not dependent. Therefore, they need a way to determine whether or not a sender is dependent.
In the section get_completion_signatures
we show how the concept
dependent_sender
can be
implemented in terms of this paper’s
get_completion_signatures
function template. By making this a public-facing concept, we give
sender adaptor authors a way to do early type-checking, just like the
standard adaptors.
A significant fraction of
std::execution
has been
implemented with this design change. It can be found on Compiler Explorer1 and in this
GitHub gist2. This implementation includes all
the design elements that would stress the completion signature
computation including:
Both dependent and non-dependent senders and sender adaptors, with tests for both early and late diagnosis of type errors.
The exposition-only
basic-sender
has been
implemented with the changes
get_completion_signatures
customization syntax and the new
customization, which has been used to implement and type-check all the
sender types, as specified.impls-for
<Tag>::check-types
A sender that is implemented via a lowering to another sender
type via a lazy transform_sender
customization
(stopped_as_optional
).
Using awaitables as senders and vice versa.
[ Editor's note: This wording is relative to the current working draft with the addition of [P3164R4] ]
[ Editor's note: Change [async.ops]/13 as follows: ]
13 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 ifis
MATCHING-SIG
(Sig, F)true
([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 non-dependent sender is a sender type whose completion signatures are knowable independent of an execution environment.
[ Editor's note: Change [execution.syn] as follows: ]
Header
<execution>
synopsis [execution.syn]namespace std::execution { … as before … template<class Sndr, class
...
Env> concept sender_in = see below;= env<>
template<class Sndr>
concept dependent_sender = see below;
template<class Sndr, class Rcvr> concept sender_to = see below; template<class... Ts> structtype-list
; // exposition only// [exec.getcomplsigs], completion signatures
struct get_completion_signatures_t;
[ Editor's note:inline constexpr get_completion_signatures_t get_completion_signatures {};
This alias is moved below and modified.
]template<class Sndr, class... Env>
requires sender_in<Sndr, Env...>
template<class... Ts> usingusing completion_signatures_of_t =
call-result-t
<get_completion_signatures_t, Sndr, Env...>;decayed-tuple
= tuple<decay_t<Ts>...>; // exposition only … as before … template<class Sndr, class...
Env> usingsingle-sender-value-type
=see below
; // exposition only template<class Sndr, class...
Env> conceptsingle-sender
=see below
; // exposition only … as before … // [exec.util], sender and receiver utilities // [exec.util.cmplsig]completion signatures
template<class Fn> conceptcompletion-signature
=see below
; // exposition only template<completion-signature
... Fns> struct completion_signatures; template<class Sigs> concept{}
valid-completion-signatures
=see below
; // exposition onlystruct dependent_sender_error : exception {};
// [exec.getcomplsigs]
template<class Sndr, class... Env>
consteval auto get_completion_signatures() ->
valid-completion-signatures
auto;template<class Sndr, class... Env>
requires sender_in<Sndr, Env...>
using completion_signatures_of_t = decltype(get_completion_signatures<Sndr, Env...>());
// [exec.util.cmplsig.trans]
template<
valid-completion-signatures
InputSignatures,
valid-completion-signatures
AdditionalSignatures = completion_signatures<>,template<class...> class SetValue =
see below
,template<class> class SetError =
see below
,
valid-completion-signatures
SetStopped = completion_signatures<set_stopped_t()>>using transform_completion_signatures = completion_signatures<
see below
>;template<
sender Sndr,
class Env = env<>,
valid-completion-signatures
AdditionalSignatures = completion_signatures<>,template<class...> class SetValue =
see below
,template<class> class SetError =
see below
,
valid-completion-signatures
SetStopped = completion_signatures<set_stopped_t()>>requires sender_in<Sndr, Env>
using transform_completion_signatures_of =
transform_completion_signatures<
completion_signatures_of_t<Sndr, Env>,
// [exec.run.loop], run_loop class run_loop; … as before … }AdditionalSignatures, SetValue, SetError, SetStopped>;
1 The exposition-only type
is defined as follows … as before
variant-or-empty
<Ts...>2 For type
sSndr
and pack of typesEnv
, letCS
becompletion_signatures_of_t<Sndr, Env...>
. Thensingle-sender-value-type<Sndr, Env
is ill-formed if...
>CS
is ill-formed or ifsizeof...(Env) > 1
istrue
; otherwise, it is an alias for:
(2.1)
value_types_of_t<Sndr, Env
if that type is well-formed,
gather-signatures
<set_value_t, CS, decay_t, type_identity_t>(2.2) Otherwise,
void
if
value_types_of_t<Sndr, Env
is
gather-signatures
<set_value_t, CS, tuple, variant>variant<tuple<>>
orvariant<>
,(2.3) Otherwise,
value_types_of_t<Sndr, Env
if that type is well-formed,
gather-signatures
<set_value_t, CS,decayed-tuple
, type_identity_t>(2.4) Otherwise,
is ill-formed.
single-sender-value-type
<Sndr, Env...
>3 The exposition-only concept
single-sender
is defined as follows:namespace std::execution { template<class Sndr, class
...
Env> conceptsingle-sender
= sender_in<Sndr, Env...
> && requires { typenamesingle-sender-value-type
<Sndr, Env...
>; }; }? A type satisfies and models the exposition-only concept
valid-completion-signatures
if it is a specialization of thecompletion_signatures
class template.
[ Editor's note: Modify [exec.snd.general] as follows: ]
1 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)
get_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: [ Editor's note: Reformatted into a list. ]
(1.1) 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.(1.2)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.
[ Editor's note: In [exec.snd.expos], change para 2 as follows: ]
2 For a queryable object
env
,is an expression whose type satisfies
FWD-ENV
(env)queryable
such that for a query objectq
and a pack of subexpressionsas
, the expressionis ill-formed if
FWD-ENV
(env).query(q, as...)forwarding_query(q)
isfalse
; otherwise, it is expression-equivalent toenv.query(q, as...)
. The typeis
FWD-ENV-T
(Env)decltype(
.FWD-ENV
(declval<Env>()))
[ Editor's note: In [exec.snd.expos], insert the following paragraph after para 22 and before para 23 (thus moving the exposition-only concept out of para 24 and into its own para so it can be used from elsewhere): ]
? Let
valid-specialization
be the following concept:template<template<class...> class T, class... Args>
valid-specialization
= requires { typename T<Args...>; }; // exposition only concept
[ Editor's note: In [exec.snd.expos] para 23 add the mandate shown below: ]
template<class Tag, class Data =
see below
, class... Child> constexpr automake-sender
(Tag tag, Data&& data, Child&&... child);23 Mandates: The following expressions are
true
:
(23.4)
dependent_sender<Sndr> || sender_in<Sndr>
, whereSndr
isas defined below.
basic-sender
<Tag, Data, Child...>Recommended practice: When this mandate fails because
get_completion_signatures<Sndr>()
would exit with an exception, implementations are encouraged to include information about the exception in the resulting diagnostic.
[ Editor's note: In
[exec.snd.expos] para 24, change the definition of the exposition-only
basic-sender
template
as follows: ]
24 Returns: A prvalue of type
that has been direct-list-initialized with the forwarded arguments, where
basic-sender
<Tag, decay_t<Data>, decay_t<Child>...>basic-sender
is the following exposition-only class template except as noted below.namespace std::execution { template<class Tag> concept
completion-tag
= // exposition only <Tag, set_value_t> || same_as<Tag, set_error_t> || same_as<Tag, set_stopped_t>; same_astemplate<template<class...> class T, class... Args>
structconcept
valid-specialization
= requires { typename T<Args...>; }; // exposition onlydefault-impls
{ // exposition only static constexpr autoget-attrs
=see below
; static constexpr autoget-env
=see below
; static constexpr autoget-state
=see below
; static constexpr autostart
=see below
; static constexpr autocomplete
=see below
;template<class Sndr, class... Env>
static consteval void
}; … as before …check-types
();template <class Sndr>
using
template <class Sndr, size_t I = 0> usingdata-type
= decltype(declval<Sndr>().templateget
<1>()); // exposition onlychild-type
= decltype(declval<Sndr>().templateget
<I+2>()); // exposition only … as before …template<class Sndr, class... Env>
template<class Tag, class Data, class... Child> structusing
completion-signatures-for
=see below
; // exposition onlybasic-sender
:product-type
<Tag, Data, Child...> { // exposition only using sender_concept = sender_t; usingindices-for
= index_sequence_for<Child...>; // exposition only decltype(auto) get_env() const noexcept { auto& [_, data, ...child] = *this; returnimpls-for
<Tag>::get-attrs
(data, child...); } template<decays-to
<basic-sender
> Self, receiver Rcvr> auto connect(this Self&& self, Rcvr rcvr) noexcept(see below
) ->basic-operation
<Self, Rcvr> { return {std::forward<Self>(self), std::move(rcvr)}; } template<decays-to
<basic-sender
> Self, class...
Env>static constexpr
auto get_completion_signatures();
(this Self&& self, Env&&... env) noexcept
->
completion-signatures-for
<Self, Env...> {return {};
}; }}
[ Editor's note: In [exec.snd.expos], replace para 39 with the paragraphs shown below and renumber subsequent paragraphs: ]
39 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 typeis well-formed, let
basic-operation
<Sndr, Rcvr>op
be an lvalue subexpression of that type. Thendenotes a specialization of
completion-signatures-for
<Sndr, Env>completion_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,is ill-formed. If
completion-signatures-for
<Sndr, Env>is well-formed and its type is not dependent upon the type
completion-signatures-for
<Sndr, Env>Env
,is well-formed and denotes the same type; otherwise,
completion-signatures-for
<Sndr>is ill-formed.
completion-signatures-for
<Sndr>template <class Sndr, class... Env>
default-impls
::check-types
(); static consteval void? Let
Is
be the pack of integral template arguments of theinteger_sequence
specialization denoted by.
indices-for
<Sndr>? Effects: Equivalent to:
child-type
<Sndr, Is>,FWD-ENV-T
(Env)...>(), ...) (get_completion_signatures<? Remarks: For any types
T
,S
, and packE
, lete
be the expression. Then exactly one of the following is
impls-for
<T>::check-types
<S, E...>()true
:
(?.1)
e
is ill-formed, or(?.3) The evaluation of
e
exits with an exception, or(?.2)
e
is a core constant expression.When
e
is a core constant expression, the typesS
,E...
uniquely determine a set of completion signatures.template<class Tag, class Data, class... Child> template <class Sndr, class... Env>
basic-sender
<Tag, Data, Child...>::get_completion_signatures(); constexpr auto? Let
Rcvr
be the type of a receiver whose environment has typeE
, whereE
is the first type in the listEnv..., env<>
. Letbe the expression
CHECK-TYPES
(), and let
impls-for
<Tag>::templatecheck-types
<Sndr, E>()CS
be a type determined as follows:
(?.1) If
is a core constant expression, let
CHECK-TYPES
()op
be an lvalue subexpression whose type isconnect_result_t<Sndr, Rcvr>
. ThenCS
is the specialization ofcompletion_signatures
the set of whose template arguments correspond to the set of completion operations that are potentially evaluated ([basic.def.odr]) as a result of evaluatingop.start()
.(?.2) Otherwise,
CS
iscompletion_signatures<>
.? Constraints:
is a well-formed expression.
CHECK-TYPES
()? Effects: Equivalent to
CHECK-TYPES
(); return CS();
[ Editor's note: Add the following new paragraphs to the end of [exec.snd.expos] ]
?template<class... Fns>
overload-set
: Fns... { struct using Fns::operator()...; };[ Editor's note: The following is moved from [exec.on] para 6 and modified. ]
??
not-a-sender
{ struct using sender_concept = sender_t; template<class Sndr> static constexpr auto get_completion_signatures() -> completion_signatures<> { throw unspecified; } };
decay-copyable-result-datums(auto cs)
{ constexpr voidfor-each
([]<class Tag, class... Ts>(Tag(*)(Ts...)) { cs. if constexpr (!(is_constructible_v<decay_t<Ts>, Ts> &&...))unspecified
; throw }); }
[ Editor's note: Change [exec.snd.concepts] para 1 and add a new para after 1 as follows: ]
1 The
sender
concept … as before … to produce an operation state.namespace std::execution {
template<class Sigs>
concept
valid-completion-signatures
=see below
; // exposition onlytemplate<auto>
concept
is-constant
= true;// exposition only
template<class Sndr> conceptis-sender
= // exposition only <typename Sndr::sender_concept, sender_t>; derived_from template<class Sndr> conceptenable-sender
= // exposition onlyis-sender
<Sndr> ||is-awaitable
<Sndr,env-promise
<env<>>>; // [exec.awaitable]template<class Sndr>
consteval bool
is-dependent-sender-helper
() try {// exposition only
get_completion_signatures<Sndr>();
return false;
} catch (dependent_sender_error&) {
return true;
}
template<class Sndr> concept sender = bool(enable-sender
<remove_cvref_t<Sndr>>) && requires (const remove_cvref_t<Sndr>& sndr) { { get_env(sndr) } ->queryable
; } && <remove_cvref_t<Sndr>> && move_constructible<remove_cvref_t<Sndr>, Sndr>; constructible_from template<class Sndr, class...
Env> concept sender_in = <Sndr> && sender= env<>
(sizeof...(Env) <= 1) &&
(
queryable<Env>&&...)
&&
is-constant
<get_completion_signatures<Sndr, Env...>()>;requires (Sndr&& sndr, Env&& env) {
{ get_completion_signatures(std::forward<Sndr>(sndr), std::forward<Env>(env)) }
->
valid-completion-signatures
;};
template<class Sndr>
concept dependent_sender =
sender<Sndr> && bool_constant<
template<class Sndr, class Rcvr> concept sender_to = <Sndr, env_of_t<Rcvr>> && sender_in<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>> && receiver_ofrequires (Sndr&& sndr, Rcvr&& rcvr) { connect(std::forward<Sndr>(sndr), std::forward<Rcvr>(rcvr)); }; }is-dependent-sender-helper
<Sndr>()>::value;
- ? For a type
Sndr
, ifsender<Sndr>
istrue
anddependent_sender<Sndr>
isfalse
, thenSndr
is a non-dependent sender ([exec.async.ops]).
[ Editor's note: Strike [exec.snd.concepts] para 3 (this para is moved to [execution.syn]): ]
3
A type models the exposition-only conceptvalid-completion-signatures
if it denotes a specialization of thecompletion_signatures
class template.
[ Editor's note: 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<>
): ]
4 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 onlysender_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...)>;
[ Editor's note: Change [exec.awaitable] p 1-4 as follows: ]
1 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.2 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<
behaves asnone-such
>coroutine_handle<void>
.3 Let
is-awaitable
be the following exposition-only concept: [ Editor's note: NB: there are added ellipses in the following code block. ]template<class T> concept
await-suspend-result
=see below
; template<class A, class...
Promise> conceptis-awaiter
= // exposition only requires (A& a, coroutine_handle<Promise...
> h) { .await_ready() ? 1 : 0; a{ a.await_suspend(h) } ->await-suspend-result
; .await_resume(); a}; template<class C, class...
Promise> conceptis-awaitable
= requires (C (*fc)() noexcept, Promise&...
p) { {GET-AWAITER
(fc(), p...
) } ->is-awaiter
<Promise...
>; };
is
await-suspend-result
<T>true
if and only if one of the following istrue
:4 For a subexpression
c
such thatdecltype((c))
is typeC
, and an lvaluep
of typePromise
,denotes the type
await-result-type
<C, Promise>decltype(
andGET-AWAITER
(c, p).await_resume())denotes the type
await-result-type
<C>decltype(
.GET-AWAITER
(c).await_resume())
[ Editor's note: Change [exec.getcomplsigs] as follows: ]
1
get_completion_signatures
is a customization point object. Letsndr
be an expression such thatdecltype((sndr))
isSndr
, and letenv
be an expression such thatdecltype((env))
isEnv
. Letnew_sndr
be the expressiontransform_sender(decltype(
, and letget-domain-late
(sndr, env)){}, sndr, env)NewSndr
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:
(1.1)
decltype(new_sndr.get_completion_signatures(env))
if that type is well-formed,(1.2) Otherwise,
remove_cvref_t<NewSndr>::completion_signatures
if that type is well-formed,(1.3) Otherwise, if
is
is-awaitable
<NewSndr,env-promise
<Env>>true
, then:completion_signatures<
SET-VALUE-SIG
(await-result-type
<NewSndr,env-promise
<Env>>), // ([exec.snd.concepts]) set_error_t(exception_ptr), set_stopped_t()>
- (1.4) Otherwise,
CS
is ill-formed.
template <class Sndr, class... Env>
; consteval auto get_completion_signatures() ->
valid-completion-signatures
auto? Let
except
be an rvalue subexpression of an unspecified class typeExcept
such thatmove_constructible<Except> && derived_from<Except, exception>
istrue
. Letbe
CHECKED-COMPLSIGS
(e
)e
ife
is a core constant expression whose type satisfiesvalid-completion-signatures
; otherwise, it is the following expression:
e
, throwexcept
, completion_signatures()) (Let
be expression-equivalent to
get-complsigs
<Sndr, Env...>()remove_reference_t<Sndr>::template get_completion_signatures<Sndr, Env...>()
, and letNewSndr
beSndr
ifsizeof...(Env) == 0
istrue
; otherwise,decltype(
wheres
)s
is the following expression:transform_sender(
get-domain-late
(declval<Sndr>(), declval<Env>()...), declval<Sndr>(), declval<Env>()...)? Constraints:
sizeof...(Env) <= 1
istrue
.? Effects: Equivalent to:
return e;
wheree
is expression-equivalent to the following:
(?.1)
if
CHECKED-COMPLSIGS
(get-complsigs
<NewSndr, Env...>())is a well-formed expression.
get-complsigs
<NewSndr, Env...>()(?.2) Otherwise,
if
CHECKED-COMPLSIGS
(get-complsigs
<NewSndr>())is a well-formed expression.
get-complsigs
<NewSndr>()(?.3) Otherwise,
completion_signatures<
SET-VALUE-SIG
(await-result-type
<NewSndr,env-promise
<Env>...>), // ([exec.snd.concepts]) set_error_t(exception_ptr), set_stopped_t()>if
is
is-awaitable
<NewSndr,env-promise
<Env>...>true
.(?.4) Otherwise,
(throw
ifdependent-sender-error
(), completion_signatures())sizeof...(Env) == 0
istrue
, wheredependent-sender-error
isdependent_sender_error
or an unspecified type derived publicly and unambiguously fromdependent_sender_error
.(?.5) Otherwise,
(throw
.except
, completion_signatures())? 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.2 Let
rcvr
be an rvalue whose type [ Editor's note: ... as before ].
[ Editor's note: At the very bottom of [exec.connect], change the Mandates of para 6 as follows: ]
6 The expression
connect(sndr, rcvr)
is expression-equivalent to:
(6.1)
new_sndr.connect(rcvr)
if that expression is well-formed.Mandates: The type of the expression above satisfies
operation_state
.(6.2) Otherwise,
.
connect-awaitable
(new_sndr, rcvr)Mandates:
The following aresender<Sndr> && receiver<Rcvr>
true
:
[ Editor's note: In [exec.read.env] para 3, make the following change: ]
3 The exposition-only class template
impls-for
([exec.snd.general]) is specialized forread_env
as follows:namespace std::execution { template<> struct
impls-for
<decayed-typeof
<read_env>> :default-impls
{ static constexpr autostart
= [](auto query, auto& rcvr) noexcept -> void {TRY-SET-VALUE
(std::move(rcvr), query(get_env(rcvr))); };template<class Sndr, class Env>
static consteval void
}; }check-types
();
template<class Sndr, class Env>
check-types
(); static consteval void
[ Editor's note: 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 to.
FWD-ENV
(get_env(rcvr))(3.?) An adaptor whose child senders are all non-dependent ([async.ops]) is itself non-dependent.
(3.5) These requirements apply to any function that is selected by the implementation of the sender adaptor.
(3.?) 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.
[ Editor's note: Change [exec.write.env] as follows (see [P3284R4] for [exec.write.env], to be voted on in Bulgaria): ]
1
write_env
is a sender adaptor that accepts a sender and a queryable object, and that returns a sender that, when connected with a receiverrcvr
, connects the adapted sender with a receiver whose execution environment is the result of joining the queryable argumentenv
to the result ofget_env(rcvr)
.2
write_env
is a customization point object. For some subexpressionssndr
andenv
, ifdecltype((sndr))
does not satisfysender
or ifdecltype((env))
does not satisfyqueryable
, the expressionwrite_env(sndr, env)
is ill-formed. Otherwise, it is expression-equivalent tomake-sender(write_env, env, sndr)
.3 Let
write-env-t
denote the typedecltype(auto(write_env))
. The exposition-only class templateimpls-for
([exec.snd.expos]) is specialized forwrite-env-t
as follows:template<> struct
impls-for
<write-env-t
> :default-impls
{static constexpr auto
join-env
(const auto& state, const auto& env) noexcept {return
see below
;}
static constexpr autoget-env
= [](auto, const auto& state, const auto& rcvr) noexcept { return
see below
; };
join-env
(state,FWD-ENV
(get_env(rcvr)))template<class Sndr, class... Env>
static consteval void
};check-types
();Invocation of
returns an object
impls-for
<write-env-t
>::get-env
join-env
e
such that
(3.1)
decltype(e)
modelsqueryable
and(3.2) given a query object
q
, the expressione.query(q)
is expression-equivalent tostate.query(q)
if that expression is valid; otherwise,e.query(q)
is expression-equivalent to.
FWD-ENV(get_env(rcvr))
env
.query(q)
- (3.3) For type
Sndr
and pack of typesEnv
, letState
beand let
data-type
<Sndr>JoinEnv
be the packdecltype(
. Thenjoin-env
(declval<State>(),FWD-ENV
(declval<Env>())))is expression-equivalent to
impls-for
<write-env-t
>::check-types
<Sndr, Env...>()get_completion_signatures<
.child-type
<Sndr>, JoinEnv...>()
[ Editor's note: Change [exec.schedule.from] para 4 and insert a new para between 6 and 7 as follows: ]
4 The exposition-only class template
impls-for
([exec.snd.general]) is specialized forschedule_from_t
as follows:namespace std::execution { template<> struct
impls-for
<schedule_from_t> :default-impls
{ static constexpr autoget-attrs
=see below;
static constexpr autoget-state
=see below;
static constexpr autocomplete
=see below;
template<class Sndr, class... Env>
static consteval void
}; }check-types
();5 The member … as before …
6 The member
is initialized with a callable object equivalent to the following lambda: [ Editor's note: This integrates the resolution from LWG#4203. ]
impls-for
<schedule_from_t>::get-state
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(see below) requires sender_in<child-type<Sndr>,
env_of_t<Rcvr>
FWD-ENV-T
()
> { auto& [_, sch, child] = sndr; … as before …template<class Sndr, class... Env>
check-types
(); static consteval void? Effects: Equivalent to:
data-type
<Sndr>>,FWD-ENV-T
(Env)...>(); get_completion_signatures<schedule_result_t<child-type
<Sndr>,FWD-ENV-T
(Env)...>(); auto cs = get_completion_signatures<decay-copyable-result-datums
(cs); // see [exec.snd.expos]7 Objects of the local class
state-type
… as before …8 Let
Sigs
be a pack of the arguments to thecompletion_signatures
specialization named bycompletion_signatures_of_t<
. Letchild-type
<Sndr>,FWD-ENV-T
(env_of_t<Rcvr>)
>as-tuple
be an alias template such thatdenotes the type
as-tuple
<Tag(Args...)>. Then
decayed-tuple
<Tag, Args...>variant_t
denotes the typevariant<monostate,
, except with duplicate types removed.as-tuple
<Sigs>...>
[ Editor's note: Change
[exec.on] para 6 as follows
(not-a-sender
is moved
to [exec.snd.expos]): ]
6 Otherwise: Let
not-a-scheduler
be an unspecified empty class type., and letnot-a-sender
be the exposition-only type:
not-a-sender
{ struct using sender_concept = sender_t; auto get_completion_signatures(auto&&) const { return see below; } };
where the member functionget_completion_signatures
returns an object of a type that is not a specialization of thecompletion_signatures
class template.
[ Editor's note: Delete [exec.on] para 9 as follows: ]
9
Recommended practice: Implementations should use the return type ofto inform users that their usage of on is incorrect because there is no available scheduler onto which to restore execution.
not-a-sender
::get_completion_signatures
[ Editor's note: Change [exec.then] para 4 as follows: ]
4 The exposition-only class template
impls-for
([exec.snd.general]) is specialized forthen-cpo
as follows:namespace std::execution { template<> struct
impls-for
<decayed-typeof
<then-cpo
>> :default-impls
{ static constexpr auto complete = []<class Tag, class... Args> (auto, auto& fn, auto& rcvr, Tag, Args&&... args) noexcept -> void { if constexpr (same_as<Tag,decayed-typeof
<set-cpo
>>) {TRY-SET-VALUE
(rcvr, (std::move(fn), std::forward<Args>(args)...)); invoke} else { ()(std::move(rcvr), std::forward<Args>(args)...); Tag} };template<class Sndr, class... Env>
static consteval void
}; }check-types
();
?template<class Sndr, class... Env>
check-types
(); static consteval void
(?.1) Effects: Equivalent to:
child-type
<Sndr>,FWD-ENV-T
(Env)...>(); auto cs = get_completion_signatures< auto fn = []<class... Ts>(set_value_t(*)(Ts...)) {data-type
<Sndr>>, Ts...>) if constexpr (!invocable<remove_cvref_t<unspecified
; throw };for-each
(overload-set
{fn, [](auto){}}); cs.
[ Editor's note: Change [exec.let] paras 5 and 6 and insert a new para after 6 as follows: ]
5 The exposition-only class template
impls-for
([exec.snd.general]) is specialized forlet-cpo
as follows:namespace std::execution { template<class State, class Rcvr, class... Args> void
let-bind
(State& state, Rcvr& rcvr, Args&&... args); // exposition only template<> structimpls-for
<decayed-typeof
<let-cpo
>> :default-impls
{ static constexpr autoget-state
=see below
; static constexpr autocomplete
=see below
;template<class Sndr, class... Env>
static consteval void
}; }check-types
();6 Let
receiver2
denote the following exposition-only class template:namespace std::execution { … as before … }
Invocation of the function
returns an object
receiver2
::get_enve
such that
(6.1)
decltype(e)
modelsqueryable
and(6.2) given a query object
q
, the expressione.query(q)
is expression-equivalent toif that expression is valid
env
.query(q),; otherwise, if the type ofq
satisfiesforwarding-query
,e.query(q)
is expression-equivalent toget_env(
; otherwise,rcvr
).query(q)e.query(q)
is ill-formed.?template<class Sndr, class... Env>
check-types
(); consteval void
(?.1) Effects: Equivalent to:
data-type
<Sndr>>; using LetFn = remove_cvref_t<child-type
<Sndr>,FWD-ENV-T
(Env)...>(); auto cs = get_completion_signatures<decayed-typeof
<set-cpo
>(*)(Ts...)) { auto fn = []<class... Ts>(is-valid-let-sender
) if constexpr (!unspecified
; throw };for-each
(overload-set
(fn, [](auto){})); cs.where
is-valid-let-sender
istrue
if and only if all of the following aretrue
:
- (?.1.1)
(constructible_from<decay_t<Ts>, Ts> &&...)
- (?.1.2)
invocable<LetFn, decay_t<Ts>&...>
- (?.1.3)
sender<invoke_result_t<LetFn, decay_t<Ts>&...>>
- (?.1.4)
sizeof...(Env) == 0 || sender_in<invoke_result_t<LetFn, decay_t<Ts>&...>,
env-t
...>where
env-t
is the packdecltype(
.let-cpo
.transform_env(declval<Sndr>(), declval<Env>()))[ Editor's note: The following changes are the proposed resolutions to cplusplus/sender-receiver#316 and cplusplus/sender-receiver#318. ]
7
is initialized with a callable object equivalent to the following:
impls-for
<decayed-typeof
<let-cpo
>>::get-state
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires
see below
{ auto& [_, fn, child] = sndr; using fn_t = decay_t<decltype(fn)>; using env_t = decltype(let-env
(child)); using args_variant_t =see below
; using ops2_variant_t =see below
; … as before … }8 Let
Sigs
be a pack of the arguments to thecompletion_signatures
specialization named bycompletion_signatures_of_t<
. Letchild-type
<Sndr>,FWD-ENV-T
(env_of_t<Rcvr>)
>LetSigs
be a pack of those types inSigs
with a return type of. Let
decayed-typeof
<set-cpo
>as-tuple
be an alias template such thatdenotes the type
as-tuple
<Tag(Args...)>. Then
decayed-tuple
<Args...>args_variant_t
denotes the typevariant<monostate,
except with duplicate types removed.as-tuple
<LetSigs>...>9 Given a type
Tag
and a packArgs
, letas-sndr2
be an alias template such thatdenotes the type
as-sndr2
<Tag(Args...)>. Then
call-result-t
<Fn, decay_t<Args>&...>ops2_variant_t
denotes the type<monostate, connect_result_t<
as-sndr2
<LetSigs>, receiver2<Rcvr,Env
env_t
>>...> variantexcept with duplicate types removed.
10 The
requires-clause
constraining the above lambda is satisfied if and only if the typesargs_variant_t
andops2_variant_t
are well-formed.11 The exposition-only function template
let-bind
has effects equivalent to: … as before …12 … as before …
[ Editor's note: The following change to [exec.let] para 13 is the proposed resolution to cplusplus/sender-receiver#319. ]
13 Let
sndr
andenv
be subexpressions, and letSndr
bedecltype((sndr))
. Ifis
sender-for
<Sndr,decayed-typeof
<let-cpo
>>false
, then the expressionis ill-formed. Otherwise, it is equal to
let-cpo
.transform_env(sndr, env).
JOIN-ENV
(let-env
(sndr),FWD-ENV
(env))
auto& [_, _, child] = sndr;
return
JOIN-ENV
(let-env
(child),FWD-ENV
(env));
[ Editor's note: Change [exec.bulk] para 3 and insert a new para after 5 as follows: ]
3 The exposition-only class template
impls-for
([exec.snd.general]) is specialized forbulk_t
as follows:namespace std::execution { template<> struct
impls-for
<bulk_t> :default-impls
{ static constexpr autocomplete
=see below
;template<class Sndr, class... Env>
static consteval void
}; }check-types
();4 The member
is … as before …
impls-for
<bulk_t>::complete
5 … as before …
?template<class Sndr, class... Env>
check-types
(); consteval void
(?.1) Effects: Equivalent to:
child-type
<Sndr>,FWD-ENV-T
(Env)...>(); auto cs = get_completion_signatures< auto fn = []<class... Ts>(set_value_t(*)(Ts...)) {data-type
<Sndr>>, Ts&...>) if constexpr (!invocable<remove_cvref_t<unspecified
; throw };for-each
(overload-set
{fn, [](auto){}}); cs.
6
Let the subexpression out_sndr
denote … as before
…
[ Editor's note: Change [exec.split] para 3 and insert a new para after 3 as follows: ]
3 The name
split
denotes a pipeable sender adaptor object.For a subexpressionsndr
, letSndr
bedecltype((sndr))
. Ifsender_in<Sndr,
issplit-env
>false
,split(sndr)
is ill-formed.
? The exposition-only class template
impls-for
([exec.snd.general]) is specialized forsplit_t
as follows:namespace std::execution { template<>
impls-for
<split_t> :default-impls
{ struct template<class Sndr>check-types
() { static consteval voidchild-type
<Sndr>,split-env
>(); auto cs = get_completion_signatures<decay-copyable-result-datums
(cs); // see [exec.snd.expos] } }; }
[ Editor's note: Change [exec.when.all] paras 2-9 and insert two new paras after 4 as follows: ]
2 The names
when_all
andwhen_all_with_variant
denote customization point objects. Letsndrs
be a pack of subexpressions, letSndrs
be a pack of the typesdecltype((sndrs))...
, and letCD
be the typecommon_type_t<decltype(
, and letget-domain-early
(sndrs))...>CD2
beCD
ifCD
is well-formed, anddefault_domain
otherwise. The expressionswhen_all(sndrs...)
andwhen_all_with_variant(sndrs...)
are ill-formed if any of the following is true:
- (2.3)
CD
is ill-formed.3 The expression
when_all(sndrs...)
is expression-equivalent to:(
CD()
CD2()
,make-sender
(when_all, {}, sndrs...)) transform_sender4 The exposition-only class template
impls-for
([exec.snd.general]) is specialized forwhen_all_t
as follows:namespace std::execution { template<> struct
impls-for
<when_all_t> :default-impls
{ static constexpr autoget-attrs
=see below
; static constexpr autoget-env
=see below
; static constexpr autoget-state
=see below
; static constexpr autostart
=see below
; static constexpr autocomplete
=see below
;template<class Sndr, class... Env>
static consteval void
}; }check-types
();? Let
make-when-all-env
be the following exposition-only function template:template<class Env>
make-when-all-env
(inplace_stop_source& stop_src, Env&& env) noexcept { constexpr autosee below
; return }Returns an object
e
such that [ Editor's note: The following itemized list has been moved here from para 6 and modified as indicated. ]
(?.1)
decltype(e)
modelsqueryable
, and(?.2)
e.query(get_stop_token)
is expression-equivalent tostop_src.get_token()
, and(?.3) given a query object
q
with type other than cvstop_token_t
and whose type satisfiesforwarding-query
,e.query(q)
is expression-equivalent toenv.query(q)
.Let
when-all-env
be an alias template such thatdenotes the type
when-all-env
<Env>decltype(
.make-when-all-env
(declval<inplace_stop_source&>(), declval<Env>()))
?template<class Sndr, class... Env>
check-types
(); consteval void
(?.1) Let
Is
be the pack of integral template arguments of theinteger_sequence
specialization denoted by.
indices-for
<Sndr>(?.2) Effects: Equivalent to:
auto fn = []<class Child>() {
when-all-env
<Env>...>(); auto cs = get_completion_signatures<Child,count-of
(set_value) >= 2) if constexpr (cs.unspecified
; throwdecay-copyable-result-datums
(cs); // see [exec.snd.expos] };child-type
<Sndr, Is>>(), ...); (fn.template operator()<(?.3) Throws: Any exception thrown as a result of evaluating the Effects, or an exception of an unspecified type derived from
exception
whenCD
is ill-formed.
5 The member
… as before …
impls-for
<when_all_t>::get-attrs
6 The member
is initialized with a callable object equivalent to the following lambda expression:
impls-for
<when_all_t>::get-env
[]<class State, class Rcvr>(auto&&, State& state, const Receiver& rcvr) noexcept { return
see below
; }
make-when-all-env
(state.stop-src
, get_env(rcvr))
Returns an objecte
such that
7 The member
is initialized with a callable object equivalent to the following lambda expression:
impls-for
<when_all_t>::get-state
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(e) -> decltype(e) { return e; }
where
e
is the expression::forward<Sndr>(sndr).apply(
make-state
<Rcvr>()) stdand where
make-state
is the following exposition-only class template:
template<class Sndr, class Env>
concept
max-1-sender-in
= sender_in<Sndr, Env> &&@// exposition only
enum class disposition { started, error, stopped }; // exposition only template<class Rcvr> struct make-state { template<(tuple_size_v<value_types_of_t<Sndr, Env, tuple, tuple>> <= 1);
class... Sndrs> auto operator()(auto, auto, Sndrs&&... sndrs) const { using values_tuple = see below; using errors_variant = see below; using stop_callback = stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>; … as before …
max-1-sender-in
<env_of_t<Rcvr>>8 Let
copy-fail
be … as before …9 The alias
values_tuple
denotes the type<value_types_of_t<Sndrs,
env_of_t<Rcvr>
FWD-ENV-T
()
, decayed-tuple, optional>...> tupleif that type is well-formed; otherwise,
tuple<>
.
[ Editor's note: Change [exec.when.all] para 14 as follows: ]
14 The expression
when_all_with_variant(sndrs...)
is expression-equivalent to:(
CD()
CD2()
,make-sender
(when_all_with_variant, {}, sndrs...)); transform_sender
[ Editor's note: Change [exec.into.variant] paras 4-5 as follows (with the change to para 5 being a drive-by fix): ]
4 The exposition-only class template
impls-for
([exec.snd.general]) is specialized forinto_variant
as follows:namespace std::execution { template<> struct
impls-for
<into_variant_t> :default-impls
{ static constexpr autoget-state
=see below
; static constexpr autocomplete
=see below
;template<class Sndr, class... Env>
static consteval void
check-types
() {auto cs = get_completion_signatures<
child-type
<Sndr>,FWD-ENV-T
(Env)...>();// see [exec.snd.expos]
decay-copyable-result-datums
(cs);}
}; }5 The member
is initialized with a callable object equivalent to the following lambda:
impls-for
<into_variant_t>::get-state
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> type_identity<value_types_of_t<
child-type
<Sndr>,env_of_t<Rcvr>
FWD-ENV-T
()
>> { return {}; }
[ Editor's note: Change [exec.stopped.opt] as follows. Note: this includes the proposed resolution to cplusplus/sender-receiver#311 ]
2 The name
stopped_as_optional
denotes a pipeable sender adaptor object. For a subexpressionsndr
, letSndr
bedecltype((sndr))
. The expressionstopped_as_optional(sndr)
is expression-equivalent to:(
get-domain-early
(sndr),make-sender
(stopped_as_optional, {}, sndr)) transform_senderexcept that
sndr
is only evaluated once.? The exposition-only class template
impls-for
([exec.snd.general]) is specialized forstopped_as_optional_t
as follows:template<>
impls-for
<stopped_as_optional_t> :default-impls
{ struct template<class Sndr, class... Env>check-types
() { static consteval voiddefault-impls
::check-types
<Sndr, Env...>(); if constexpr (!requires {single-sender-value-type
<child-type
<Sndr>,FWD-ENV-T
(Env)...>>); }) requires (!same_as<void, throw unspecified; } };3 Let
sndr
andenv
be subexpressions such thatSndr
isdecltype((sndr))
andEnv
isdecltype((env))
. Ifis
sender-for
<Sndr, stopped_as_optional_t>false
, or if the typethen the expressionis ill-formed or
single-sender-value-type
<Sndr, Env>void
,stopped_as_optional.transform_sender(sndr, env)
is ill-formed; otherwise, ifsender_in<
ischild-type
<Sndr>,FWD-ENV-T
(Env)>false
, the expressionstopped_as_optional.transform_sender(sndr, env)
is equivalent to; otherwise, it is equivalent to:
not-a-sender
()auto&& [_, _, child] = sndr; using V =
single-sender-value-type
<Sndr
child-type
<>
,Env
FWD-ENV-T
()
>; return let_stopped( (std::forward_like<Sndr>(child), then[]<class... Ts>(Ts&&... ts) noexcept(is_nothrow_constructible_v<V, Ts...>) { return optional<V>(in_place, std::forward<Ts>(ts)...); }), []() noexcept { return just(optional<V>()); });
[ Editor's note: Change [exec.sync.wait] as follows: ]
4 The name
this_thread::sync_wait
denotes a customization point object. For a subexpressionsndr
, letSndr
bedecltype((sndr))
.IfThe expressionsender_in<Sndr,
issync-wait-env
>false
, 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:(
get-domain-early
(sndr), sync_wait, sndr) apply_senderMandates:
[ Editor's note: Change [exec.sync.wait.var] as follows: ]
1 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,
issync-wait-env
>false
, 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:(
get-domain-early
(sndr), sync_wait_with_variant, sndr) apply_senderMandates:
(1.?)
sender_in<Sndr,
issync-wait-env
>true
.(1.1) The type
is well-formed.
sync-wait-with-variant-result-type
<Sndr>(1.2)
same_as<decltype(
ise
),sync-wait-with-variant-result-type
<Sndr>>true
, wheree
is theapply_sender
expression above.2
IfThe expressionis
callable
<sync_wait_t, Sndr>false
, the expressionsync_wait_with_variant.apply_sender(sndr)
is ill-formed. Otherwise, itsync_wait_with_variant.apply_sender(sndr)
is equivalent to …as before
[ Editor's note: Change [exec.util.cmplsig] para 8 and add a new para after 8 as follows: ]
8namespace std::execution { template<
completion-signature
... Fns> struct completion_signatures {template<class Tag>
static constexpr size_t
count-of
(Tag) { returnsee below
; }template<class Fn>
static constexpr void
for-each
(Fn&& fn) { // exposition only(std::forward<Fn>(fn)(static_cast<Fns*>(nullptr)), ...);
}
}; … as before … }
? For a subexpression
tag
, letTag
be the decayed type oftag
.completion_signatures<Fns...>::
returns the count of function types incount-of
(tag)Fns...
that are of the formTag(Ts...)
whereTs
is a pack of types.
[ Editor's note: Remove subclause [exec.util.cmplsig.trans]. ]
[ Editor's note: Change [exec.run.loop.types] para 5 as follows: ]
5
run-loop-sender
is an exposition-only type that satisfiessender
.For any typeEnv
,completion_signatures_of_t<
isrun-loop-sender
, Env>completion_signatures<set_value_t(), set_error_t(exception_ptr), set_stopped_t()>
.
I would like to thank Hana Dusíková for her work making constexpr exceptions a reality for C++26. Thanks are also due to David Sankel for his encouragement to investigate using constexpr exceptions as an alternative to TMP hackery, and for giving feedback on an early draft of this paper.
write_env
and
unstoppable
Sender Adaptors.