-
Notifications
You must be signed in to change notification settings - Fork 6
/
receiver_redesign.bs
1793 lines (1404 loc) · 79.7 KB
/
receiver_redesign.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
Title: Removing exception_ptr from the Receiver Concepts
H1: <code>Removing exception_ptr from the Receiver Concepts</code>
Shortname: P2532
Revision: 0
Status: P
Group: WG21
Audience: LEWG
Editor: Eric Niebler, [email protected]
URL: https://wg21.link/P2532
!Source: <a href="https://github.com/brycelelbach/wg21_p2300_std_execution/blob/main/receiver_redesign.bs">GitHub</a>
Issue Tracking: GitHub https://github.com/brycelelbach/wg21_p2300_std_execution/issues
Metadata Order: Editor, This Version, Source, Issue Tracking, Project, Audience
Markup Shorthands: markdown yes
Toggle Diffs: no
No Abstract: yes
Default Biblio Display: direct
</pre>
<style>
pre {
margin-top: 0px;
margin-bottom: 0px;
}
table, th, tr, td {
border: 2px solid black !important;
}
@media (prefers-color-scheme: dark) {
table, th, tr, td {
border: 2px solid white !important;
}
}
.ins, ins, ins *, span.ins, span.ins * {
background-color: rgb(200, 250, 200);
color: rgb(0, 136, 0);
text-decoration: none;
}
.del, del, del *, span.del, span.del * {
background-color: rgb(250, 200, 200);
color: rgb(255, 0, 0);
text-decoration: line-through;
text-decoration-color: rgb(255, 0, 0);
}
math, span.math {
font-family: serif;
font-style: italic;
}
ul {
list-style-type: "— ";
}
blockquote {
counter-reset: paragraph;
}
div.numbered, div.newnumbered {
margin-left: 2em;
margin-top: 1em;
margin-bottom: 1em;
}
div.numbered:before, div.newnumbered:before {
position: absolute;
margin-left: -2em;
display-style: block;
}
div.numbered:before {
content: counter(paragraph);
counter-increment: paragraph;
}
div.newnumbered:before {
content: "�";
}
div.numbered ul, div.newnumbered ul {
counter-reset: list_item;
}
div.numbered li, div.newnumbered li {
margin-left: 3em;
}
div.numbered li:before, div.newnumbered li:before {
position: absolute;
margin-left: -4.8em;
display-style: block;
}
div.numbered li:before {
content: "(" counter(paragraph) "." counter(list_item) ")";
counter-increment: list_item;
}
div.newnumbered li:before {
content: "(�." counter(list_item) ")";
counter-increment: list_item;
}
</style>
# Introduction # {#intro}
This paper proposes a refactorization of the receiver concepts of [[P2300R4]] to
address concerns raised by LEWG during its design review related to the
requirement of an error channel that accepts `exception_ptr`. The change to
`receiver_of` proposed herein enables a corresponding change to the `sender_to`
concept that strengthens type checking and removes some need to constrain
customizations of the `connect` customization point.
## Motivation ## {#motivation}
In [[P2300R4]], the receiver concepts are currently expressed as follows:
```c++
template <class T, class E = exception_ptr>
concept receiver =
move_constructible<remove_cvref_t<T>> &&
constructible_from<remove_cvref_t<T>, T> &&
requires(remove_cvref_t<T>&& t, E&& e) {
{ execution::set_stopped(std::move(t)) } noexcept;
{ execution::set_error(std::move(t), (E&&) e) } noexcept;
};
template<class T, class... An>
concept receiver_of =
receiver<T> &&
requires(remove_cvref_t<T>&& t, An&&... an) {
execution::set_value(std::move(t), (An&&) an...);
};
```
During the design review of P2300, LEWG raised the following concerns about the
form of these concepts:
1. Since `set_value` is permitted to be potentially throwing, and since the
receiver type is not known when a sender is asked to compute its completion
signatures, most senders will need to pessimistically report that they can
complete exceptionally, when that may in fact not be true. This may cause the
instantiation of expensive error handling code that is effectively dead.
2. No receiver `R` can satisfy the `receiver<R>` or `receiver_of<R, As...>`
concepts without providing an error channel for `exception_ptr`. This has the
following problems:
* `exception_ptr` is a relatively heavy-weight error type, not unlike a
`shared_ptr`. Requiring the presence of this channel is likely to cause
needless code generation.
* It makes it questionable whether any of P2300 can be reasonably expected
to work in freestanding environments, which often lack exception
handling support.
Although the design of P2300 is sound, LEWG nevertheless wanted an investigation
into these issues and a recommendation to be made.
This paper makes a recommendation to change the receiver concepts to address
these concerns.
## Design Summary ## {#design-summary}
This paper proposes to make the following changes, summarized here without
commentary. Commentary is provided below.
* Remove the default implementation of the `get_env` receiver query.
* The `receiver_of` concept takes a receiver and an instance of the
`completion_signatures<>` class template.
* A receiver's customization of `set_value` is required to be `noexcept`.
* The `sender_to<Sndr, Rcvr>` concept requires `Rcvr` to accept all of `Sndr`'s
completions.
* `connect(sndr, rcvr)` also requires `rcvr` to accept all of `sndr`'s completions.
* `get_completion_signatures` is required to return an instantiation of the
`completion_signatures` class template; the `value_types_of_t` and
`error_types_of_t` template aliases remain unchanged.
* The `make_completion_signatures` design is slightly tweaked to be more general.
## Design Rationale ## {#design-rationale}
The author believes these are all reasonable adjustments to the design of P2300,
but one may wonder why they were not considered before now.
The fourth revision of P2300 brought with it some notable changes, the two most
significant of which are:
1. Support for dependently-typed senders, where a sender's completions can
depend on information that isn't known independently of the execution
environment within which the sender will be initiated. For instance,
a `get_scheduler()` sender which queries the receiver for the current
scheduler and then sends it through the value channel, cannot possibly
know the type of the scheduler it will send until it has been connected
to a receiver.
2. Dropping of support for "untyped" senders, which do not declare their
completion signatures. Untyped senders were supported because of the lack
of dependently-typed senders, which ceased to be an issue with R4. At the
direction of LEWG, "untyped" senders were dropped, greatly simplifying
the design.
Taken together, these two changes open up a huge piece of the design space. The
implication is that <b>a sender is <i>always</i> able to provide its completion
signatures.</b> This is new, and P2300R4 is not taking advantage of this extra
type information.
The author realized that the extra type information can be leveraged to
accommodate LEWGs requests regarding the receiver interface, while at the same
time simplifying uses of `std::execution` by permitting the library to take on
more of the type checking burden.
The `sender_to` concept, which checks whether a sender and a receiver can be
connected, now has perfect information: it can ask the receiver for the execution
environment; it can ask the sender how it will complete when initiated in that
environment; and it can ask the receiver if it is capable of receiving all of
the sender's possible completions. This was not possible before R4.
Below we look at each of the changes suggested in the summary and explain its
rationale in light of the extra information now available to the type system.
# Design Details # {#design-details}
## Remove the default implementation of the `get_env` receiver query. ## {#no-get-env-default}
The presence of a customization of `get_env` becomes the distinguishing feature
of receivers. A "receiver" no longer needs to provide any completion channels at
all to be considered a receiver, only `get_env`.
## The `receiver_of` concept takes a receiver and an instance of the `completion_signatures<>` class template. ## {#receiver-of-completion-signatures}
The `receiver_of` concept, rather than accepting a receiver and some value
types, is changed to take a receiver and an instance of the
`completion_signatures<>` class template. A sender uses
`completion_signatures<>` to describe the signals with which it completes. The
`receiver_of` concept ensures that a particular receiver is capable of receiving
those signals.
Notably, if a sender only sends a value (i.e., can never send an error or a
stopped signal), then a receiver need only provide a value channel to be
compatible with it.
## A receiver's customization of `set_value` is required to be `noexcept`. ## {#noexcept-set-value}
This makes it possible for many senders to become "no-fail"; that is, they
cannot complete with an error. `just(1)`, for instance, will only ever
successfully send an integer through the value channel. An adaptor such as
`then(sndr, fun)` can check whether `fun` can ever exit exceptionally when
called with all the sets of values that `sndr` may complete with. If so, the
`then` sender must add `set_error_t(exception_ptr)` to its list of completions.
Otherwise, it need not.
## The `sender_to<Sndr, Rcvr>` concept requires `Rcvr` to accept all of `Sndr`'s completions. ## {#sender-to-requirements}
The `sender_to` concept, which checks whether a sender and a receiver can be
connected, now enforces that the sender's completion signatures can in fact be
handled by the receiver. Previously, it only checked that `connect(sndr, rcvr)`
was well-formed, relying on sender authors to properly constrain their `connect`
customizations.
## `connect(sndr, rcvr)` also requires `rcvr` to accept all of `sndr`'s completions. ## {#connect-requirements}
For good measure, the `connect` customization point also checks whether a
receiver can receive all of the sender's possible completions before trying to
dispatch via `tag_invoke` to a `connect` customization. This often entirely
frees sender authors from having to constrain their `connect` customizations at
all. It is enough to customize `get_completion_signatures`, and the type
checking is done automatically.
Strictly speaking, with this change, the change to `sender_to` is unnecessary.
The change to `sender_to` results in better diagnostics, in the author's
experience.
## `get_completion_signatures` is required to return an instantiation of the `completion_signatures` class template. ## {#get-completion-signatures}
`get_completion_signatures` was added in R4 in response to feedback that
authoring sender traits was too difficult/arcane. Rather than defining a struct
with `template template` aliases, a user can simply declare a sender's
completions as:
```c++
execution::completion_signatures<
execution::set_value_t(int),
execution::set_error_t(std::exception_ptr),
execution::set_stopped_t()>
```
In R4, `completion_signatures` generated the `template template` aliases for
you. The proposed change is to take it further and *require*
`get_completion_signatures` to return an instance of the `completion_signatures`
class template. With this change, the last vestige of the old sender traits
design with its unloved `temlate template` alias interface is swept away.
`completion_signatures` entirely replaces sender traits, further simplifying the
design.
The `sender` concept enforces the new requirement.
## The `value_types_of_t` and `error_types_of_t` template aliases remain. ## {#sender-traits-aliases}
It can still be helpful sometimes to *consume* the old `template template`, say,
for generating a variant of the tuples of all the sets of a sender's value
types. For that reason, the alias templates `value_types_of_t` and
`error_types_of_t` retain the same interface and semantic as before. For
instance, generating the variant of tuples of value types, you would use the
following:
```c++
execution::value_types_of_t<
Sndr,
Env,
std::tuple,
std::variant>;
```
Additionally, these two alias joined by a `sends_stopped<Sndr, Env>` Boolean
variable template to complete the set.
## The `make_completion_signatures` design is slightly tweaked to be more general. ## {#make-completion-signatures}
In the proposed design, `completion_signatures` plays a much larger role.
Accordingly, the job of specifying the completion signatures of custom sender
adaptors also becomes more important, necessitating better tools. The
`make_completion_signatures`, new to R4, narrowly misses being that better tool.
In R4, `make_completion_signatures` has the following interface:
<pre highlight="c++">
template <
execution::sender Sndr,
class Env = execution::no_env,
class OtherSigs = execution::completion_signatures<>,
template <class...> class SetValue = <i>default-set-value</i>,
template <class> class SetError = <i>default-set-error</i>,
bool SendsStopped = execution::completion_signatures_of_t<Sndr, Env>::sends_stopped>
requires sender<Sndr, Env>
using make_completion_signatures =
execution::completion_signatures</* see below */>;
</pre>
In the R4 design, `SetValue` and `SetError` are alias templates, instantiations
of which are required to name function types whose return types are
`excecution::set_value_t` and `execution::set_error_t`, respectively. This is
overly-restrictive. The problems with it are:
1. It is not possible to map one kind of completion into a different kind. For
instance, the `upon_error(sndr, fun)` maps error completions into value
completions.
2. It is not possible to map a single completion signature into multiple
different completions. For instance, the `let_value(sndr, fun)` sender
adaptor needs to map a set of `sndr`'s value types into the set of
completions of whatever sender that is returned from `fun(values...)`, which
is likely more than one.
In addition, the final Boolean `SendsStopped` parameter merely controls whether
or not the completion `execution::set_stopped_t()` should be added to the
resulting list of completion signatures. This doesn't help a sender adaptor
such as `let_stopped(sndr, fun)`, which needs to transform a stopped signal
into the set of completions of the sender that `fun()` returns.
This design proposes to change the three final template arguments as follows:
* <u><b>`template <class...> class SetValue`:</b></u> Instantiations of this alias
template must name an instantiation of the `completion_signatures` class
template.
* <u><b>`template <class> class SetError`:</b></u> Instantiations of this alias
template must name an instantiation of the `completion_signatures` class
template.
* <u><b>`class SetStopped`:</b></u> Must name an instantiation of the
`completion_signatures` class template. If the sender `Sndr` can complete
with `set_stopped`, then these signatures are included in the resulting list
of completions. Otherwise, this template parameter is ignored.
The semantics of `make_completion_signatures` is likewise simplified: The three
template arguments, `SetValue`, `SetError`, and `SetStopped`, are used to map
each of a sender's completions into a list of completions which are all
concatenated together, along with any additional signatures specified by the
`OtherSigs` list, and made unique.
# Considerations # {#considerations}
## Implications of `noexcept` `set_value` ## {#nothrow-set-value}
The role of `execution::set_value` is to execute a continuation on the success
of the predecessor. A continuation is arbitrary code, and surely arbitrary code
can exit exceptionally, so how can we require `execution::set_value` to be
`noexcept`?
The answer has two parts:
1. `execution::set_value` always has the option of accepting arguments by
forwarding reference and executing any potentially throwing operations
within a `try`/`catch` block, routing any exceptions to
`set_error(std::exception_ptr)`.
2. A sender knows what types it will send and with what value category. The
`sender_to` concept checks that none of the `set_value` expression(s) it
will execute are potentially throwing. This doesn't necessitate that all
receivers accept all arguments by forwarding reference, however. For
instance, if a sender knows it will pass an rvalue `std::string` to the
receiver's `set_value`, and if the sender is connected to a receiver whose
`set_value` takes a `std::string` by value, that will type-check. The
`sender_to` concept will essentially be enforcing this constraint:
<pre highlight="c++">
requires (Receiver rcvr) {
{ execution::set_value(std::move(rcvr), std::string()) } noexcept;
}
</pre>
Since `std::string`'s move constructor is `noexcept`, this constraint
is satisfied regardless of whether `rcvr`'s `set_value` customization
accepts the string by value or by reference.
## Diagnostics ## {#diagnostics}
On the whole, the authors of P2300 feel that this design change is the right one
to make to meet LEWG's requirements. It comes with one drawback, however: The
satisfaction checking of the `receiver_of` concept, which must now check against
a set of signatures specified in a type-list, now requires metaprogramming in
addition to `requires` clauses. As a result, diagnostics can suffer.
During the implementation experience, the author was able to surface a
relatively suscinct and accurate error for, say, the lack of a particular
completion channel on a receiver, by employing several tricks. While regrettable
that such tricks are required, we do not feel that the issue of mediocre
diagnostics is dire enough to offset the many advantages of the design presented
here.
In addition, the author has discovered a way that an implementation may choose
to extend the `connect` customization point in a way that permits users to
bypass the constraint checking entirely, thus generating a deep instantiation
backtrace that often greatly assists the debugging of custom
sender/receiver-based algorithms. This mechanism can be enshrined in the standard
as "recommended practice."
# Open questions # {#open-questions}
## Weasel wording for `-fno-exceptions` ## {#fno-exceptions}
We may need to add some weasel wording to the effect that:
> ... if an implementation is able to deduce that all of its operations are not
> potentially throwing, a conforming implementation of the algorithms in
> <section> may omit `set_error_t(exception_ptr)` from any sender's list of
> completion signatures.
If an implementation doesn't support exceptions, e.g., if the user is compiling
with `-fno-exceptions`, it can safely assume that an expression `expr` is not
going to exit exceptionally regardless of the value of `noexcept(expr)`. An
implementation shouldn't be required to report that it can complete with an
exception in that case.
## Error channel of allocating algorithms ## {#allocations}
An interesting question is what to do on freestanding implementations for those
algorithms that necessarily must allocate. Those algorithms, as P2300 stands
today, will always have a `set_error_t(exception_ptr)` completion signature. The
possibilities I see are:
* Permit implementations to omit the exceptional completion signature when it
knows allocations can't fail with an exception (see above),
* Replace the exceptional completion signature with
`set_error_t(std::error_code)`, and call the receiver with
`std::make_error_code(std::errc::not_enough_memory)` on allocation failure.
* Replace the exceptional completion signature with
`set_error_t(std::bad_alloc)`; that is, pass an instance of the
`std::bad_alloc` exception type through the error channel by value. (From
what the author can infer, freestanding implementations are required to
provide the `std::bad_alloc` type even when actually throwing exceptions is
not supported.)
# Implementation experience # {#implementation-experience}
The design described above has been implemented in a branch of the reference
implementation which can be found in the following GitHub pull request:
https://github.com/brycelelbach/wg21_p2300_std_execution/pull/410.
The change, while somewhat disruptive to the reference implementation itself,
had the benefits described above; namely:
* Stricter type-checking "for free". Sender authors need only report the
completion signatures, and the concepts and customization points of the
library do all the heavy lifting to make sure the capabilities of receivers
match the requirements of the senders.
* More "no-fail" senders. Many fewer of the senders need an error channel at
all, and the ones that do generally need it only conditionally, when working
with potentially-thrwoing callables or types whose special operations can
throw. Only those few senders that must dynamically allocate state necessarily
need a `set_error_t(exception_ptr)` channel, and we may even choose to change
those to use something like `set_error_t(bad_alloc)` instead.
* No required `set_error_t(exception_ptr)` or `set_stopped_t()` channels at all.
In addition, in the author's opinion, the reference implementation got
significantly *simpler* for the change, and the pull request removes more lines
than it adds, while adding functionality at the same time.
# Proposed wording # {#proposed-wording}
The following changes are relative to [[P2300R4]].
## Header `<execution>` synopsis
In [exec.syn], apply the following changes:
<pre highlight="c++">
namespace std::execution {
// [exec.recv], receivers
template <class T<del>, class E = exception_ptr</del>>
concept receiver = <i>see-below</i>;
template <class T, <del>class... An</del><ins>class Completions</ins>>
concept receiver_of = <i>see-below</i>;
...
<del>
template<class S>
concept <i>has-sender-types</i> = <i>see-below</i>; // exposition only</del>
...
template <class E> <ins>// arguments are not associated entities ([lib.tmpl-heads])</ins>
struct dependent_completion_signatures;
...
template<class S,
class E = no_env,
template <class...> class Tuple = <i>decayed-tuple</i>,
template <class...> class Variant = <i>variant-or-empty</i>>
requires sender<S, E>
using value_types_of_t =
<del>typename completion_signatures_of_t<S, S>::template value_types<Tuple, Variant></del>
<ins><i>see below</i></ins>;
template<class S,
class E = no_env,
template <class...> class Variant = <i>variant-or-empty</i>>
requires sender<S, E>
using error_types_of_t =
<del>typename completion_signatures_of_t<S, S>::template error_types<Variant></del>
<ins><i>see below</i></ins>;
<ins>
template<class S, class E = no_env>
requries sender<S, E>
inline constexpr bool sends_stopped = <i>see below</i>;</ins>
...
// [exec.utils.cmplsigs]
template <<i>completion-signature</i>... Fns><del> // arguments are not associated entities ([lib.tmpl-heads])</del>
struct completion_signatures<ins> {}</ins>;
template <class... Args> <i>// exposition only</i>
using <i>default-set-value</i> =
<ins>completion_signatures<</ins>set_value_t(Args...)<ins>></ins>;
template <class Err> <i>// exposition only</i>
using <i>default-set-error</i> =
<ins>completion_signatures<</ins>set_error_t(Err)<ins>></ins>;
<ins>
template <class Sigs, class E> // exposition only
concept <i>valid-completion-signatures</i> = <i>see below</i>;</ins>
// [exec.utils.mkcmplsigs]
template <
sender Sndr,
class Env = no_env,
<del>class</del><ins><i>valid-completion-signatures<Env></i></ins> AddlSigs = completion_signatures<>,
template <class...> class SetValue = <i><del>/* see below */</del><ins>default-set-value</ins></i>,
template <class> class SetError = <i><del>/* see below */</del><ins>default-set-error</ins></i>,
<del>bool SendsStopped = completion_signatures_of_t<Sndr, Env>::sends_stopped></del>
<ins><i>valid-completion-signatures<Env></i> SetStopped = completion_signatures<set_stopped_t()>></ins>
requires sender<Sndr, Env>
using make_completion_signatures = completion_signatures<<i>/* see below */</i>>;
</pre>
## `execution::get_env`
Change [exec.get_env] as follows:
1. `get_env` is a customization point object. For some subexpression `r`, `get_env(r)` is expression-equivalent to
1. `tag_invoke(execution::get_env, r)` if that expression is well-formed.
<ins>
* <i>Mandates:</i> The decayed type of the above expression is not `no_env`.</ins>
2. Otherwise, <del><code><i>empty-env</i>{}</code></del><ins>`get_env(r)` is ill-formed</ins>.
## Receivers
In [exec.recv], replace paragraphs 1 and 2 with the following:
<div class="ins">
1. A <i>receiver</i> represents the continuation of an asynchronous operation.
An asynchronous operation may complete with a (possibly empty) set of
values, an error, or it may be cancelled. A receiver has three principal
operations corresponding to the three ways an asynchronous operation may
complete: `set_value`, `set_error`, and `set_stopped`. These are
collectively known as a receiver’s <i>completion-signal operations</i>.
2. The `receiver` concept defines the requirements for a receiver type with an
unknown set of completion signatures. The `receiver_of` concept defines the
requirements for a receiver type with a known set of completion signatures.
<pre highlight=c++>
template<class T>
concept receiver =
move_constructible<remove_cvref_t<T>> &&
constructible_from<remove_cvref_t<T>, T> &&
requires(const remove_cvref_t<T>& t) {
execution::get_env(t);
};
template <class Signature, class T>
concept <i>valid-completion-for</i> = <i>// exposition only</i>
requires (Signature* sig) {
[]<class Ret, class... Args>(Ret(*)(Args...))
requires nothrow_tag_invocable<Ret, remove_cvref_t<T>, Args...>
{}(sig);
};
template <class T, class Completions>
concept receiver_of =
receiver<T> &&
requires (Completions* completions) {
[]<<i>valid-completion-for</i><T>...Sigs>(completion_signatures<Sigs...>*)
{}(completions);
};
</pre>
</div>
## `execution::set_value`
Change [exec.set_value] as follows:
1. `execution::set_value` is used to send a <i>value completion signal</i> to a receiver.
2. The name `execution::set_value` denotes a customization point object. The
expression `execution::set_value(R, Vs...)` for some subexpressions `R` and
`Vs...` is expression-equivalent to:
1. `tag_invoke(execution::set_value, R, Vs...)`, if that expression is
valid. If the function selected by `tag_invoke` does not send the
value(s) `Vs...` to the receiver `R`’s value channel, the behavior of
calling `execution::set_value(R, Vs...)` is undefined.
<ins>
* <i>Mandates:</i> The `tag_invoke` expression above is not potentially
throwing.</ins>
2. Otherwise, `execution::set_value(R, Vs...)` is ill-formed.
## Senders
Change [exec.snd] as follows:
1. A sender describes a potentially asynchronous operation. A sender's responsibility is to fulfill the receiver contract of a connected receiver by delivering one of the receiver completion-signals.
2. The `sender` concept defines the requirements for a sender type. The `sender_to` concept defines the requirements for a sender type capable of being connected with a specific receiver type.
<pre highlight=c++>
<div class="del">template<template<template<class...> class, template<class...> class> class>
struct <i>has-value-types</i>; // exposition only
template<template<template<class...> class> class>
struct <i>has-error-types</i>; // exposition only
template<class S>
concept <i>has-sender-types</i> = // exposition only
requires {
typename <i>has-value-types</i><S::template value_types>;
typename <i>has-error-types</i><S::template error_types>;
typename bool_constant<S::sends_stopped>;
};</div>
<div class="ins">template <class T, template <class...> class C>
inline constexpr bool <i>is-instance-of</i> = false; // exposition only
template <class... Ts, template <class...> class C>
inline constexpr bool <i>is-instance-of</i><C<Ts...>, C> = true;
template <class Sigs, class E>
concept <i>valid-completion-signatures</i> = // exposition only
<i>is-instance-of</i><Sigs, completion_signatures> ||
(
same_as<Sigs, dependent_completion_signatures<no_env>> &&
same_as<E, no_env>
);</div>
template <class S, class E>
concept <i>sender-base</i> = // exposition only
<del>requires { typename completion_signatures_of_t<S, E>; } &&
<i>has-sender-types</i><completion_signatures_of_t<S, E>></del>
<ins>requires (S&& s, E&& e) {
{ get_completion_signatures(std::forward<S>(s), std::forward<E>(e)) } ->
<i>valid-completion-signatures</i><E>;
}</ins>;
template<class S, class E = no_env>
concept sender =
<i>sender-base</i><S, E> &&
<i>sender-base</i><S, no_env> &&
move_constructible<remove_cvref_t<S>>;
template<class S, class R>
concept sender_to =
sender<S, env_of_t<R>> &&
<del>receiver<R> &&</del>
<ins>receiver_of<R, completion_signatures_of_t<S, env_of_t<R>>> &&</ins>
requires (S&& s, R&& r) {
execution::connect(std::forward<S>(s), std::forward<R>(r));
};
</pre>
3. The `sender_of` concept defines the requirements for a sender type that on successful completion sends the specified set of value types.
<pre highlight=c++>
template<class S, class E = no_env, class... Ts>
concept sender_of =
sender<S, E> &&
same_as<
<i>type-list</i><Ts...>,
<del>typename completion_signatures_of_t<S, E>::template value_types<<i>type-list</i>, type_identity_t></del>
<ins>value_types_of_t<S, E, <i>type-list</i>, type_identity_t></ins>
>;
</pre>
## `execution::completion_signatures_of_t`
Change [exec.sndtraitst]/p4 as follows:
4. `execution::get_completion_signatures` is a customization point object. Let
`s` be an expression such that `decltype((s))` is `S`, and let `e` be an
expression such that `decltype((e))` is `E`. Then
`get_completion_signatures(s)` is expression-equivalent to
`get_completion_signatures(s, no_env{})` and `get_completion_signatures(s,
e)` is expression-equivalent to:
1. `tag_invoke_result_t<get_completion_signatures_t, S, E>{}` if that expression is well-formed,
<ins>
* <i>Mandates:</i> <code><i>is-instance-of</i><Sigs,
completion_signatures></code> or <code><i>is-instance-of</i><Sigs,
dependent_completion_signatures></code>, where `Sigs` names the type
`tag_invoke_result_t<get_completion_signatures_t, S, E>`.</ins>
2. Otherwise, if `remove_cvref_t<S>::completion_signatures` is well-formed
and names a type, then a <ins>value-initialized</ins> prvalue of <ins>type</ins>
`remove_cvref_t<S>::completion_signatures`,
<ins>
* <i>Mandates:</i> <code><i>is-instance-of</i><Sigs,
completion_signatures></code> or <code><i>is-instance-of</i><Sigs,
dependent_completion_signatures></code>, where `Sigs` names the type `remove_cvref_t<S>::completion_signatures`.</ins>
3. Otherwise, [...]
## `dependent_completion_signatures`
Change [exec.depsndtraits] as follows:
<pre highlight="c++">
template <class E> <ins>// arguments are not associated entities ([lib.tmpl-heads])</ins>
struct dependent_completion_signatures<ins> {}</ins>;
</pre>
1. `dependent_completion_signatures` is a placeholder completion signatures
descriptor that can be <del>used</del><ins>returned from `get_completion_signatures`</ins> to report
that a type might be a sender within a particular execution environment, but
it isn't a sender in an arbitrary execution environment.
<div class="del">
2. If `decay_t<E>` is `no_env`, `dependent_completion_signatures<E>` is equivalent to:
<pre highlight="c++">
template <>
struct dependent_completion_signatures<no_env> {
template <template <class...> class, template <class...> class>
requires false
using value_types = <i>/* unspecified */</i>;
template <template <class...> class>
requires false
using error_types = <i>/* unspecified */</i>;
static constexpr bool sends_stopped = <i>/* unspecified */</i>;
};
</pre>
Otherwise, `dependent_completion_signatures<E>` is an empty struct.
</div>
<div class="ins">
2. When used as the return type of a customization of
`get_completion_signatures`, the template argument `E` shall be the
unqualified type of the second argument.</div>
## `execution::connect`
Change [exec.connect]/p2 as follows:
2. The name `execution::connect` denotes a customization point object. For some subexpressions `s` and `r`, let `S` be `decltype((s))` and `R` be `decltype((r))`, and let `S'` and `R'` be the decayed types of `S` and `R`, respectively. If `R` does not satisfy `execution::receiver`, `execution::connect(s, r)` is ill-formed. Otherwise, the expression `execution::connect(s, r)` is expression-equivalent to:
1. `tag_invoke(execution::connect, s, r)`, if <del>that expression is valid and `S` satisfies `execution::sender`</del><ins>the constraints below are satisfied</ins>. If the function selected by `tag_invoke` does not return an operation state for which `execution::start` starts work described by `s`, the behavior of calling `execution::connect(s, r)` is undefined.
<div class="ins">
* <i>Constraints:</i>
<pre highlight="c++">
sender<S, env_of_t<R>> &&
receiver_of<R, completion_signatures_of_t<S, env_of_t<R>>> &&
tag_invocable<connect_t, S, R>
</pre>
</div>
* <i>Mandates:</i> The type of the `tag_invoke` expression above satisfies `operation_state`.
2. Otherwise, <code><i>connect-awaitable</i>(s, r)</code> if [...]
[...]
<div class="del">
The operand of the <i>requires-clause</i> of <code><i>connect-awaitable</i></code> is equivalent to `receiver_of<R>` if <code><i>await-result-type</i><S, <i>connect-awaitable-promise</i>></code> is <code><i>cv</i> void</code>; otherwise, it is <code>receiver_of<R, <i>await-result-type</i><S, <i>connect-awaitable-promise</i>>></code>.</div>
<div class="ins">
Let `Res` be <code><i>await-result-type</i><S, <i>connect-awaitable-promise</i>></code>, and let `Vs...` be an empty parameter pack if `Res` is <code><i>cv</i> void</code>, or a pack containing the single type `Res` otherwise. The operand of the <i>requires-clause</i> of <code><i>connect-awaitable</i></code> is equivalent to `receiver_of<R, Sigs>` where `Sigs` names the type:
<pre highlight="c++">
completion_signatures<
set_value_t(Vs...),
set_error_t(exception_ptr),
set_stopped_t()>
</pre></ins>
3. Otherwise, `execution::connect(s, r)` is ill-formed.
## `execution::just`
Change [exec.just] as follows:
1. `execution::just` is used to create a sender that propagates a set of values to a connected receiver.
<pre highlight=c++>
template<class... Ts>
struct <i>just-sender</i> <ins>{</ins> // exposition only
<del>: completion_signatures<set_value_t(Ts...), set_error_t(exception_ptr)> {</del>
<ins>using completion_signatures =</ins>
<ins>execution::completion_signatures<set_value_t(Ts...)>;</ins>
tuple<Ts...> vs_;
template<class R>
struct operation_state {
tuple<Ts...> vs_;
R r_;
friend void tag_invoke(start_t, operation_state& s) noexcept {
<del>try {</del>
apply([&s](Ts&... values_) {
set_value(std::move(s.r_), std::move(values_)...);
}, s.vs_);
<del>}</del>
<del>catch (...) {</del>
<del>set_error(std::move(s.r_), current_exception());</del>
<del>}</del>
}
};
template<receiver<ins>_of<completion_signatures></ins> R>
requires <del>receiver_of<R, Ts...> &&</del> (copy_constructible<Ts> &&...)
friend operation_state<decay_t<R>> tag_invoke(connect_t, const <i>just-sender</i>& j, R && r) {
return { j.vs_, std::forward<R>(r) };
}
template<receiver<ins>_of<completion_signatures></ins> R>
<del>requires receiver_of<R, Ts...></del>
friend operation_state<decay_t<R>> tag_invoke(connect_t, <i>just-sender</i>&& j, R && r) {
return { std::move(j.vs_), std::forward<R>(r) };
}
};
template<<i>movable-value</i>... Ts>
<i>just-sender</i><decay_t<Ts>...> just(Ts &&... ts) noexcept(<i>see-below</i>);
</pre>
1. <i>Effects</i>: [...]
## `execution::just_error`
Change [exec.just_error] as follows:
1. `execution::just_error` is used to create a sender that propagates an error to a connected receiver.
<pre highlight=c++>
template<class T>
struct <i>just-error-sender</i> <ins>{</ins> // exposition only
<del>: completion_signatures<set_error_t(T)> {</del>
<ins>using completion_signatures =</ins>
<ins> execution::completion_signatures<set_error_t(T)>;</ins>
T err_;
template<class R>
struct operation_state {
T err_;
R r_;
friend void tag_invoke(start_t, operation_state& s) noexcept {
set_error(std::move(s.r_), std::move(err_));
}
};
template<receiver<ins>_of<completion_signatures></ins> R>
requires <del>receiver<R, T> &&</del> copy_constructible<T>
friend operation_state<decay_t<R>> tag_invoke(connect_t, const <i>just-error-sender</i>& j, R&& r) {
return { j.err_, std::forward<R>(r) };
}
template<receiver<ins>_of<completion_signatures></ins> R>
<del>requires receiver<R, T></del>
friend operation_state<decay_t<R>> tag_invoke(connect_t, <i>just-error-sender</i>&& j, R&& r) {
return { std::move(j.err_), std::forward<R>(r) };
}
};
template<<i>movable-value</i> T>
<i>just-error-sender</i><decay_t<T>> just_error(T&& t) noexcept(<i>see-below</i>);
</pre>
1. <i>Effects</i>: [...]
## `execution::just_stopped`
Change [exec.just_stopped] as follows:
1. `execution::just_stopped` is used to create a sender that propagates a stopped signal to a connected receiver.
<pre highlight=c++>
struct <i>just-stopped-sender</i> <ins>{</ins> // exposition only
<del>: completion_signatures<set_stopped_t()> {</del>
<ins>using completion_signatures =</ins>
<ins> execution::completion_signatures<set_stopped_t()>;</ins>
template<class R>
struct operation_state {
R r_;
friend void tag_invoke(start_t, operation_state& s) noexcept {
set_stopped(std::move(s.r_));
}
};
template<receiver<ins>_of<completion_signatures></ins> R>
friend operation_state<decay_t<R>> tag_invoke(connect_t, const <i>just-stopped-sender</i>& j, R&& r) noexcept {
return { std::forward<R>(r) };
}
};
<i>just-stopped-sender</i> just_stopped() noexcept;
</pre>
1. <i>Effects</i>: Equivalent to <code><i>just-stopped-sender</i>{}</code>.
## `execution::read`
Change [exec.read]/p3 as follows:
3. <code><i>read-sender</i></code> is an exposition only class template equivalent to:
<pre highlight=c++>
template <class Tag>
struct <i>read-sender</i> { // exposition only
template<class R>
struct <i>operation-state</i> { // exposition only
R r_;
friend void tag_invoke(start_t, <i>operation-state</i>& s) noexcept try {
auto value = Tag{}(get_env(s.r_));
set_value(std::move(s.r_), std::move(value));
} catch(...) {
set_error(std::move(s.r_), current_exception());
}
};
<ins>template <class Env></ins>
<ins>requires <i>callable</i><Tag, Env></ins>
<ins>using <i>completions</i> = // exposition only</ins>
<ins>completion_signatures<</ins>
<ins>set_value_t(<i>call-result-t</i><Tag, Env>), set_error_t(exception_ptr)>;</ins>
<del>template<receiver R></del>
<del>requires <i>callable</i><Tag, env_of_t<R>> &&</del>
<del>receiver_of<R, <i>call-result-t</i><Tag, env_of_t<R>>></del>
<ins>template<class R></ins>
<ins>requires receiver_of<R, <i>completions</i><env_of_t<R>>></ins>
friend <i>operation-state</i><decay_t<R>> tag_invoke(connect_t, <i>read-sender</i>, R && r) {
return { std::forward<R>(r) };
}
<del>friend <i>empty-env</i> tag_invoke(get_completion_signatures_t, <i>read-sender</i>, auto);</del>
<ins>template<class Env></ins>
<ins>friend auto tag_invoke(get_completion_signatures_t, <i>read-sender</i>, Env)</ins>
<ins>-> dependent_completion_signatures<Env>;</ins>