forked from HowardHinnant/papers
-
Notifications
You must be signed in to change notification settings - Fork 5
/
member-customization-points.html
973 lines (823 loc) · 40.6 KB
/
member-customization-points.html
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
<!DOCTYPE HTML>
<html>
<head>
<title>Member customization points for Senders and Receivers</title>
<style>
p {text-align:justify}
li {text-align:justify}
blockquote.note
{
background-color:#E0E0E0;
padding-left: 15px;
padding-right: 15px;
padding-top: 1px;
padding-bottom: 1px;
}
ins {color:#00A000}
del {color:#A00000}
</style>
</head>
<body>
<address align=right>
Document number: P2855R1
<br/>
Audience: LEWG
<br/>
<br/>
<a href="mailto:[email protected]">Ville Voutilainen</a><br/>
2024-02-22<br/>
</address>
<hr/>
<h1 align=center>Member customization points for Senders and Receivers</h1>
<h2>Abstract</h2>
<p>
There have been various suggestions that Senders and Receivers need a new
language feature for customization points, to avoid the complexity
of ADL tag_invoke.</p>
<p>This paper makes the case that C++ already has such a language
facility, and it works just fine for the purposes of Senders and Receivers.
</p>
<p>That language facility is member functions.</p>
<p>In a nutshell, the approach in this paper is relatively straightforward;
for all non-query customization points, ADL tag_invoke overloads become
member functions.
Query customization points become query member functions that take
the query tag as an argument.</p>
<p>This is because non-queries don't need to forward calls to customization
points, but it's useful for queries to be able to forward queries.</p>
<p>In order to be able to write perfect-forwarding function templates
that work both for lvalues and rvalues, we use deduced this. When
there is no need to write a single function for both lvalues and rvalues,
a traditional non-static member function will do.
</p>
<h2>The overall highest-priority goal of this proposal is "No ADL, <em>anywhere</em>"</h2>
</body>
<h2>Quick examples</h2>
<p>
A tag_invoke customization point for start
<pre><blockquote>friend void tag_invoke(std::execution::start_t, recv_op& self) noexcept</blockquote></pre>
</p>
<p>
becomes
</p>
<p>
<pre><blockquote>void start() & noexcept</blockquote></pre>
</p>
<p>
A perfect-forwarding connect
<pre><blockquote>template <__decays_to<__t> _Self, receiver _Receiver>
requires sender_to<__copy_cvref_t<_Self, _Sender>, __receiver<_Receiver>>
friend auto tag_invoke(std::execution::connect_t, _Self&& __self, _Receiver __rcvr)
</blockquote></pre>
</p>
<p>
becomes
</p>
<p>
<pre><blockquote>template <__decays_to<__t> _Self, receiver _Receiver>
requires sender_to<__copy_cvref_t<_Self, _Sender>, __receiver<_Receiver>>
auto connect(this _Self&& __self, _Receiver __rcvr)
</blockquote></pre>
</p>
<p>
The call
<pre><blockquote>tag_invoke(std::execution::connect, std::forward<Snd>(s), r);
</blockquote></pre>
</p>
<p>
becomes
</p>
<p>
<pre><blockquote>std::forward<Snd>(s).connect(r);
</blockquote></pre>
</p>
<p>A query
<pre><blockquote>friend in_place_stop_token tag_invoke(std::execution::get_stop_token_t, const __t& __self) noexcept
</blockquote></pre>
</p>
<p>
becomes
</p>
<p>
<pre><blockquote>in_place_stop_token query(std::execution::get_stop_token_t) const noexcept
</blockquote></pre>
</p>
<h2>A note on what changed there from R0</h2>
<p>After an LEWG discussion where it was suggested that tag parameters/arguments are untoward, and a very helpful suggestion that if we have wrappers anyway, we can use nested types instead, various people discussing this came to the conclusion that that feedback is right - we don't need the tags, except for query. We can add member typedef opt-ins to operation states, like we already have in receivers, and then we don't need those tag parameters/arguments.
</p>
<p>
Furthermore, we don't need to name the query function a "tag_query". It's a query, it takes a tag, but that tag-taking doesn't need to go into the name.
It's a member function. If you manage to mix such functions in a wrapper class, don't do it. Don't multi-inherit things into your sender wrapper, don't multi-inherit a sender wrapper and something else. Or if you do, use whatever
usual techniques to disambiguate declarations and calls, but mostly
just don't do it.
</p>
<h2>What does this buy us?</h2>
<p>
First of all, two things, both rather major:
<ol>
<li>NO ADL.</li>
<li>..and that makes defining customization points *much* simpler.</li>
</ol>
</p>
<p>
A bit of elaboration on the second point: consider that earlier query
of get_stop_token in tag_invoke form. It's an example of that query
for the when_all algorithm. But what needs to be done is that
both that query (which is a hidden friend) and the when_all_t
function object type are in a detail-namespace,
and then outside that namespace, in namespace std::execution, the
type is brought into scope with a using-declaration, and the actual
function object is defined.
</p>
<p>Roughly like this:
<pre><blockquote>namespace you_will_have_trouble_coming_up_with_a_name_for_it {
template <class Snd, class Recv, class Fn>
struct my_then_operation {
opstate op;
struct t {
friend void tag_invoke(start_t, t& self) noexcept {
start(self.op_);
}
};
};
<em>// ADL-protected internal senders and receivers omitted</em>
struct my_then_t {
template <sender Snd, class Fn> <em>// proper constraints omitted</em>
sender auto operator()(Snd&& sndr, Fn&& fn) const {
<em>// the actual implementation omitted</em>
}
};
}
using you_will_have_trouble_coming_up_with_a_name_for_it::my_then_t;
constexpr my_then_t my_then{};
</blockquote></pre>
</p>
<p>
This has the effect of keeping the overload set small, when each
and every type and its customizations are meticulously defined
that way. Build times are decent, the sizes of overload sets are nicely
controlled and are small, diagnostics for incorrect calls are hopefully
fairly okay.
</p>
<p>
But that's not all there is to it. Generic code that uses such things
should wrap its template parameters into utilities that prevent ADL
via template parameters. You might see something like this gem:
<pre><blockquote>// For hiding a template type parameter from ADL
template <class _Ty>
struct _X {
using __t = struct _T {
using __t = _Ty;
};
};
template <class _Ty>
using __x = __t<_X<_Ty>>;
</blockquote></pre>
</p>
<p>
and then use it like this:
<pre><blockquote>using make_stream_env_t = stream_env<stdexec::__x<BaseEnv>>;</blockquote></pre>
</p>
<p>With member customization points, you don't need any such acrobatics.
The customization points are members. You define a customization point
as a member function, and you can just put your type directly into
whichever namespace you want (some might even use the global namespace),
and you don't need to use nested detail namespaces. Then you
call <code>foo.connect(std::execution::connect, receiver);</code> and you
don't have to do the no-ADL wrapping in your template parameters either.
</p>
<p>
In other words, the benefits of avoiding ADL for the implementation include
<ul>
<li>no need to wrap template parameters, to avoid making their associated namespaces be considered for ADL</li>
<li>no need to wrap a sender algorithm's internal operation states, senders, and receivers into nested namespaces, to limit the searched scopes when ADL would be applied on such a type</li>
<li>no need to wrap a sender algorithm into a nested namespaces and do a using-declaration in an outer one, again to limit the seached scopes when ADL would be applied on something else, wishing to avoid finding the functions in the namespace of the algorithm.</li>
</ul>
Some of those are fairly traditional ADL-taming techniques, some may be recent realizations. None of them are necessary when members are used, none. This should greatly simplify the implementation. The benefits for the users are mostly
the same, they don't need to apply any of those techniques, not for their
custom schedulers, not for their custom senders, not for their
algorithm customizations, not for anything.
</p>
</p>
<p>The definition of customization points is much simpler, to a ridiculous
extent. Using them is simpler; it's a member call, everybody knows
what that does, and many people know what scopes that looks in, and
a decent amount of people appreciate the many scopes it *doesn't* look in.</p>
<p>Composition and reuse and wrapping of customization points becomes much
easier, because it's just.. ..good old OOP, if you want to look at it that
way. We're not introducing a new language facility for which you need
to figure out how to express various function compositions and such,
the techniques and patterns are decades old, and work here as they always
worked.</p>
<h2>What are its downsides compared to a new language facility?</h2>
<p>Well, we don't do anything for users who for some reason _have_ to
use ADL customization points. But the reason for going for this approach
is that we decouple Senders and Receivers from an unknown quantity,
and avoid many or even most of the problems of using ADL customization points.
</p>
<p>Other than that, I'm not sure such downsides exist.</p>
<p>A common concern with using wrappers is that they don't work
if you have existing APIs that use the wrappees - introducing wrappers
into such situations just doesn't work, because they simply aren't
the same type, and can't be made the same type. And a further
problem is having to deal with both the wrappers and wrappees
as concrete types, and figuring out when to use which, and possibly
having to duplicate code to deal with both.
</p>
<p>
The saving grace with Senders and Receivers is that they are wrapped
everywhere all the time. Algorithms wrap senders, the wrapped senders
wrap their receivers, and resulting operation states. This wrapping
nests pretty much infinitely.
</p>
<p>For cases where you need to use a concrete sender, it's probably
type-erased, rather than being a use of a concrete target sender.
</p>
<h2>Implementation experience</h2>
<p>
A very partial work-in-progress implementation exists as a branch of
the reference implementation of P2300, at
<a href="https://github.com/villevoutilainen/wg21_p2300_std_execution/tree/P2855_member_customization_points">https://github.com/villevoutilainen/wg21_p2300_std_execution/tree/P2855_member_customization_points</a>.
</p>
<p>
The implementation has the beginnings of a change from ADL tag_invoke overloads to
non-static member functions and member functions using deduced this.
It's rather rudimentary, and very incomplete, only covering operation states
at this point.
</p>
<h2>Some additional considerations</h2>
<h3>Access</h3>
<p>
It's possible to make customization point members private, and have
them usable by the framework, by befriending the entry point (e.g.
std::execution::connect, in a member connect(std::execution::connect_t)).
It's perhaps ostensibly rare to need to do that, considering that
it's somewhat unlikely that a sender wrapper or an operation state
wrapper that provides the customization point would have oodles
of other functionality. Nevertheless, we have made that possible in the
prototype implementation, so we could do the same in the standard.
This seems like an improvement over the ADL customization points.
With them, anyone can do the ADL call, the access of a hidden friend
doesn't matter.
</p>
<h3>Interface pollution</h3>
<p>
It's sometimes plausible that a class with a member customization
point inherits another class that provides the same customization point,
and it's not an override of a virtual function. In such situations,
the traditional technique works, bring in the base customization
point via a using-declaration, which silences possible hiding warnings
and also create an overload set. The expectation is that the situation
and the technique are sufficiently well-known, since it's old-skool.
</p>
<h2>Wording</h2>
<p>
General note: the goal here is to replace tag_invokes with member
functions, and remove all ADL-mitigating techniques;
there are other changes I deemed necessary at least
for this presentation: none of the CPOs are meant to be called
unqualified after this paper's change(s). They are not customizable
as such, in and of themselves, despite being CPOs. They are entry
points. The entry points are called qualified (and in some cases
have to be; you can't just call a foo.connect() on any coroutine
result type, but you can call std::execution::connect() on it.),
and they are customized by the mechanism depicted in
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2999r3.html">P2999</a>, if the thing customized is an algorithm,
or by writing member functions, if the thing customized is not really
a customization but rather an opt-in.
</p>
<p>But note, though, that once the adoption of this paper's approach
is done, we don't <em>have</em> to
qualify <em>anything</em> in this specification, because all
calls to namespace-scope functions are <em>as-if</em> qualified, and the
rest is member calls.
</p>
<p>
Additionally, it might be tempting to remove the function objects
set_value, set_error and set_stopped completely, but there are
things that use them as generic function objects (see <em>just-sender</em> below), so that ability is left as-is.
</p>
<p>
Due to not using ADL, 16.4.6.17 Class template-heads can be removed, as it's
an ADL-mitigating technique that isn't necessary when member functions
are used for everything.
</p>
<p>
In [functional.syn], strike tag_invocable, nothrow_tag_invocable, tag_invoke_result, and tag_invoke:
<blockquote><pre><del>// [func.tag_invoke], tag_invoke
namespace <em>tag-invoke</em> { <em>// exposition only</em>
void tag_invoke();
template<class Tag, class... Args>
concept tag_invocable =
requires (Tag&& tag, Args&&... args) {
tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...);
};
template<class Tag, class... Args>
concept nothrow_tag_invocable =
tag_invocable<Tag, Args...> &&
requires (Tag&& tag, Args&&... args) {
{ tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...) } noexcept;
};
template<class Tag, class... Args>
using tag_invoke_result_t =
decltype(tag_invoke(declval<Tag>(), declval<Args>()...));
template<class Tag, class... Args>
struct tag_invoke_result<Tag, Args...> {
using type =
tag_invoke_result_t<Tag, Args...>; <em>// present if and only if tag_invocable<Tag, Args...> is true</em>
};
struct tag; <em>// exposition only</em>
}
inline constexpr <em>tag-invoke</em>::tag tag_invoke {};
using <em>tag-invoke</em>::tag_invocable;
using <em>tag-invoke</em>::nothrow_tag_invocable;
using <em>tag-invoke</em>::tag_invoke_result_t;
using <em>tag-invoke</em>::tag_invoke_result;</del>
template<auto& Tag>
using tag_t = decay_t<decltype(Tag)>;
</pre></blockquote>
</p>
<p> Remove [func.tag_invoke]</p>
<p> In [exec.general]/p4.1, replace the specification of the exposition-only
<em><code><em>mandate-nothrow-call</em></code></em> with the following:
<blockquote><ins><ol>
<li><p>For a subexpression <code><em>expr</em></code>, let
<code><em>MANDATE-NOTHROW</em>(<em>expr</em>)</code>
be expression-equivalent to <code><em>expr</em></code>.</p>
<p><em>Mandates:</em> <code>noexcept(<em>expr</em>)</code> is
<code>true</code>.</p></li></ol></ins>
</blockquote></p>
<p> In [exec.syn], remove ADL-protecting nested namespaces:
<blockquote><pre><del>namespace <em>queries</em> { <em>// exposition only</em></del>
struct forwarding_query_t;
struct get_allocator_t;
struct get_stop_token_t;
<del>}
using <em>queries</em>::forwarding_query_t;
using <em>queries</em>::get_allocator_t;
using <em>queries</em>::get_stop_token_t;</del>
namespace std::execution {
<em>// [exec.queries], queries</em>
enum class forward_progress_guarantee;
<del>namespace <em>queries</em> { <em>// exposition only</em></del>
struct get_domain_t;
struct get_scheduler_t;
struct get_delegatee_scheduler_t;
struct get_forward_progress_guarantee_t;
template<class CPO>
struct get_completion_scheduler_t;
<del>}
using <em>queries</em>::get_domain_t;
using <em>queries</em>::get_scheduler_t;
using <em>queries</em>::get_delegatee_scheduler_t;
using <em>queries</em>::get_forward_progress_guarantee_t;
using <em>queries</em>::get_completion_scheduler_t;</del>
inline constexpr get_domain_t get_domain{};
inline constexpr get_scheduler_t get_scheduler{};
inline constexpr get_delegatee_scheduler_t get_delegatee_scheduler{};
inline constexpr get_forward_progress_guarantee_t get_forward_progress_guarantee{};
template<class CPO>
inline constexpr get_completion_scheduler_t<CPO> get_completion_scheduler{};
<del>namespace <em>exec-envs</em> { <em>// exposition only</em></del>
struct empty_env {};
struct get_env_t;
<del>}
using <em>envs-envs</em>::empty_env;
using <em>envs-envs</em>::get_env_t;</del>
<em>// [exec.domain.default], domains</em>
struct default_domain;
<em>// [exec.sched], schedulers</em>
<ins>struct scheduler_t {};</ins>
template<class Sch>
concept scheduler = <em>see below</em>;
//...
<del>namespace <em>receivers</em> { <em>// exposition only</em></del>
struct set_value_t;
struct set_error_t;
struct set_stopped_t;
<del>}
using <em>receivers</em>::set_value_t;
using <em>receivers</em>::set_error_t;
using <em>receivers</em>::set_stopped_t;</del>
// ...
<del>namespace <em>op-state</em> { <em>// exposition only</em></del>
struct start_t;
<del>}
using <em>op-state</em>::start_t;</del>
<ins>struct operation_state_t {};</ins>
// ...
<del>namespace <em>completion-signatures</em> { <em>// exposition only</em></del>
struct get_completion_signatures_t;
<del>}
using <em>completion-signatures</em>::get_completion_signatures_t;</del>
// ...
<del>namespace <em>senders-connect</em> { <em>// exposition only</em></del>
struct connect_t;
<del>}
using <em>senders-connect</em>::connect_t;</del>
// ...
<del>namespace <em>senders-factories</em> { <em>// exposition only</em></del>
struct just_t;
struct just_error_t;
struct just_stopped_t;
struct schedule_t;
<del>}</del>
<del>using <em>senders-factories</em>::just_t;</del>
<del>using <em>senders-factories</em>::just_error_t;</del>
<del>using <em>senders-factories</em>::just_stopped_t;</del>
<del>using <em>senders-factories</em>::schedule_t;</del>
inline constexpr just_t just{};
inline constexpr just_error_t just_error{};
inline constexpr just_stopped_t just_stopped{};
inline constexpr schedule_t schedule{};
inline constexpr <em>unspecified</em> read{};
// ...
<del>namespace sender-adaptor-closure { <em>// exposition only</em></del>
template<<em>class-type</em> D>
struct sender_adaptor_closure { };
<del>}
using sender-adaptor-closure::sender_adaptor_closure;</del>
<del>namespace <em>sender-adaptors</em> { <em>// exposition only</em></del>
struct on_t;
struct transfer_t;
struct schedule_from_t;
struct then_t;
struct upon_error_t;
struct upon_stopped_t;
struct let_value_t;
struct let_error_t;
struct let_stopped_t;
struct bulk_t;
struct split_t;
struct when_all_t;
struct when_all_with_variant_t;
struct into_variant_t;
struct stopped_as_optional_t;
struct stopped_as_error_t;
struct ensure_started_t;
<del>}
using <em>sender-adaptors</em>::on_t;
using <em>sender-adaptors</em>::transfer_t;
using <em>sender-adaptors</em>::schedule_from_t;
using <em>sender-adaptors</em>::then_t;
using <em>sender-adaptors</em>::upon_error_t;
using <em>sender-adaptors</em>::upon_stopped_t;
using <em>sender-adaptors</em>::let_value_t;
using <em>sender-adaptors</em>::let_error_t;
using <em>sender-adaptors</em>::let_stopped_t;
using <em>sender-adaptors</em>::bulk_t;
using <em>sender-adaptors</em>::split_t;
using <em>sender-adaptors</em>::when_all_t;
using <em>sender-adaptors</em>::when_all_with_variant_t;
using <em>sender-adaptors</em>::into_variant_t;
using <em>sender-adaptors</em>::stopped_as_optional_t;
using <em>sender-adaptors</em>::stopped_as_error_t;
using <em>sender-adaptors</em>::ensure_started_t;</del>
// ...
<del>namespace <em>sender-consumers</em> { <em>// exposition only</em></del>
struct start_detached_t;
<del>}
using <em>sender-consumers</em>::start_detached_t;</del>
// ...
}
namespace std::this_thread {
<em>// [exec.queries], queries</em>
<del>namespace queries { <em>// exposition only</em></del>
struct execute_may_block_caller_t;
<del>}
using queries::execute_may_block_caller_t;</del>
inline constexpr execute_may_block_caller_t execute_may_block_caller{};
<del>namespace <em>this-thread</em> { <em>// exposition only</em></del>
struct <em>sync-wait-env</em>; <em>// exposition only</em>
template<class S>
requires sender_in<S, <em>sync-wait-env</em>>
using <em>sync-wait-type</em> = <em>see below</em>; <em>// exposition only</em>
template<class S>
using <em>sync-wait-with-variant-type</em> = <em>see below</em>; <em>// exposition only</em>
struct sync_wait_t;
struct sync_wait_with_variant_t;
<del>}
using <em>this-thread</em>::sync_wait_t;
using <em>this-thread</em>::sync_wait_with_variant_t;</del>
}
namespace std::execution {
<em>// [exec.execute], one-way execution</em>
<del>namespace <em>execute</em> { <em>// exposition only</em></del>
struct execute_t;
<del>}
using <em>execute</em>::execute_t;</del>
inline constexpr execute_t execute{};
<em>// [exec.as.awaitable]</em>
<del>namespace <em>coro-utils</em> { <em>// exposition only</em></del>
struct as_awaitable_t;
<del>}
using <em>coro-utils</em>::as_awaitable_t;</del>
<em>// [exec.with.awaitable.senders]</em>
template<<em>class-type</em> Promise>
struct with_awaitable_senders;
}
</pre></blockquote>
</p>
<p>In [exec.get.env]/1, edit as follows:
<blockquote><pre><ins>execution::</ins>get_env is a customization point object. For some subexpression o of type O, <ins>execution::</ins>get_env(o) is expression-equivalent to
<del>tag_invoke(std::get_env, </del>const_cast<const O&>(o)<ins>.get_env()</ins> if that expression is well-formed.
</pre></blockquote>
</p>
<p>In [exec.fwd.env]/2.1, edit the expression form:
<blockquote><pre><del><em>mandate-nothrow-call</em>(tag_invoke, std::forwarding_query, q)</del><ins><em>MANDATE-NOTHROW</em>(q.query(std::forwarding_query))</ins> if that expression is well-formed.
</pre></blockquote>
</p>
<p>In [exec.get.allocator]/2, edit as follows:
<blockquote><pre>The name <ins>std::</ins>get_allocator denotes a query object. For some subexpression r, <ins>std::</ins>get_allocator(r) is expression-equivalent to
<del><em>mandate-nothrow-call</em>(tag_invoke, std::get_allocator, as_const(r))</del><ins><em>MANDATE-NOTHROW</em>(as_const(r).query(std::get_allocator))</ins>.
</pre></blockquote>
</p>
<p>In [exec.get.stop.token]/2, edit as follows:
<blockquote><pre>The name <ins>std::</ins>get_stop_token denotes a query object. For some subexpression r, <ins>std::</ins>get_stop_token(r) is expression-equivalent to:
<del><em>mandate-nothrow-call</em>(tag_invoke, std::get_stop_token, as_const(r))</del><ins><em>MANDATE-NOTHROW</em>(as_const(r).query(std::get_stop_token))</ins>,
if this expression is well-formed.
</pre></blockquote>
</p>
<p>In [exec.get.scheduler]/2, edit as follows:
<blockquote><pre>The name <ins>execution::</ins>get_scheduler denotes a query object. For some subexpression r, <ins>execution::</ins>get_scheduler(r) is expression-equivalent to
<del><em>mandate-nothrow-call</em>(tag_invoke, get_scheduler, as_const(r))</del><ins><em>MANDATE-NOTHROW</em>(as_const(r).query(execution::get_scheduler))</ins>.
</pre></blockquote>
</p>
<p>In [exec.get.scheduler]/4, edit as follows:
<blockquote><pre><ins>execution::</ins>get_scheduler() (with no arguments) is expression-equivalent to execution::read(<ins>execution::</ins>get_scheduler)
</pre></blockquote>
</p>
<p>In [exec.get.delegatee.scheduler]/2, edit as follows:
<blockquote><pre>The name <ins>execution::</ins>get_delegatee_scheduler denotes a query object. For some subexpression r, <ins>execution::</ins>get_delegatee_scheduler(r) is expression-equivalent to
<del><em>mandate-nothrow-call</em>(tag_invoke, get_delegatee_scheduler, as_const(r))</del><ins><em>MANDATE-NOTHROW</em>(as_const(r).query(execution::get_delegatee_scheduler))</ins>.
</pre></blockquote>
</p>
<p>In [exec.get.forward.progress.guarantee]/2, edit as follows:
<blockquote><pre>The name <ins>execution::</ins>get_forward_progress_guarantee denotes a query object. For some subexpression s, let S be decltype((s)).
If S does not satisfy scheduler, get_forward_progress_guarantee is ill-formed.
Otherwise, <ins>execution::</ins>get_forward_progress_guarantee(s) is expression-equivalent to:
<del><em>mandate-nothrow-call</em>(tag_invoke, get_forward_progress_guarantee, as_const(s))</del>
<ins><em>MANDATE-NOTHROW</em>(as_const(s).query(execution::get_forward_progress_guarantee))</ins>, if this expression is well-formed.
</pre></blockquote>
</p>
<p>In [exec.execute.may.block.caller]/2.1, edit the expression form:
<blockquote><pre><del><em>mandate-nothrow-call</em>(tag_invoke, this_thread::execute_may_block_caller, as_const(s))</del>
<ins><em>MANDATE-NOTHROW</em>(as_const(s).query(this_thread::execute_may_block_caller))</ins>, if this expression is well-formed.
</pre></blockquote>
</p>
<p>In [exec.completion.scheduler]]/2, edit as follows:
<blockquote><pre>The name <ins>execution::</ins>get_completion_scheduler denotes a query object template. For some subexpression q, let Q be decltype((q)).
If the template argument Tag in get_completion_scheduler<Tag>(q) is not one of set_value_t, set_error_t, or set_stopped_t,
get_completion_scheduler<Tag>(q) is ill-formed. Otherwise, <ins>execution::</ins>get_completion_scheduler<Tag>(q) is expression-equivalent to
<del><em>mandate-nothrow-call</em>(tag_invoke, get_completion_scheduler<Tag>, as_const(q))</del>
<ins><em>MANDATE-NOTHROW</em>(as_const(q).query(execution::get_completion_scheduler<Tag>))</ins> if this expression is well-formed.
</pre></blockquote>
</p>
<p>In [exec.sched]/1, edit as follows:
<blockquote><pre><ins>template<class Sch>
inline constexpr bool <em>enable-scheduler</em> = <em>// exposition only</em>
requires {
requires derived_from<typename Sch::scheduler_concept, scheduler_t>;
};</ins>
template<class Sch>
concept scheduler =
<ins><em>enable-scheduler</em><remove_cvref_t<Sch>> &&</ins>
queryable<Sch> &&
requires(Sch&& sch, const get_completion_scheduler_t<set_value_t> tag) {
{ schedule(std::forward<Sch>(sch)) } -> sender;
{ <del>tag_invoke(tag, std::get_env(</del>
<ins>execution::get_env(execution::</ins>schedule(std::forward<Sch>(sch))<ins>).query(tag)</ins><del>))</del> }
-> same_as<remove_cvref_t<Sch>>;
} &&
equality_comparable<remove_cvref_t<Sch>> &&
copy_constructible<remove_cvref_t<Sch>>;
</pre></blockquote>
</p>
<p>In [exec.recv.concepts]/1, edit as follows:
<blockquote><pre>template<class Rcvr>
inline constexpr bool <del>enable_receiver</del><ins><em>enable-receiver</em> = <em>// exposition only</em></em></ins>
requires {
requires derived_from<typename Rcvr::receiver_concept, receiver_t>;
};
template<class Rcvr>
concept receiver =
<del>enable_receiver</del><ins><em>enable-receiver</em><remove_cvref_t<Rcvr>></ins> &&
requires(const remove_cvref_t<Rcvr>& rcvr) {
{ <ins>execution::</ins>get_env(rcvr) } -> queryable;
} &&
move_constructible<remove_cvref_t<Rcvr>> && <em>// rvalues are movable, and</em>
constructible_from<remove_cvref_t<Rcvr>, Rcvr>; <em>// lvalues are copyable</em>
</pre></blockquote>
</p>
<p>Strike [exec.recv.concepts]/2:
<blockquote><pre><del>Remarks: Pursuant to [namespace.std], users can specialize enable_receiver to true for <em>cv</em>-unqualified program-defined types
that model receiver, and false for types that do not. Such specializations shall be usable in constant expressions ([expr.const]) and have type const bool.</del>
</pre></blockquote>
</p>
<p>In [exec.set.value]/1, edit as follows:
<blockquote><pre><ins>execution::</ins>set_value is a value completion function ([async.ops]). Its associated completion tag is <ins>execution::</ins>set_value_t.
The expression <ins>execution::</ins>set_value(R, Vs...) for some subexpression R and pack of subexpressions Vs is ill-formed if R is an lvalue or a const rvalue.
Otherwise, it is expression-equivalent to <del><em>mandate-nothrow-call</em>(tag_invoke, set_value, R, Vs...)</del><ins><em>MANDATE-NOTHROW</em>(R.set_value<del,</del>(Vs...))</ins>.
</pre></blockquote>
</p>
<p>In [exec.set.error]/1, edit as follows:
<blockquote><pre><ins>execution::</ins>set_error is an error completion function. Its associated completion tag is <ins>execution::</ins>set_error_t.
The expression <ins>execution::</ins>set_error(R, E) for some subexpressions R and E is ill-formed if R is an lvalue or a const rvalue.
Otherwise, it is expression-equivalent to <del><em>mandate-nothrow-call</em>(tag_invoke, set_error, R, E)</del><ins><em>MANDATE-NOTHROW</em>(R.set_error(E))</ins>.
</pre></blockquote>
</p>
<p>In [exec.set.stopped]/1, edit as follows:
<blockquote><pre><ins>execution::</ins>set_stopped is a stopped completion function. Its associated completion tag is <ins>execution::</ins>set_stopped_t.
The expression <ins>execution::</ins>set_stopped(R) for some subexpression R is ill-formed if R is an lvalue or a const rvalue.
Otherwise, it is expression-equivalent to <del><em>mandate-nothrow-call</em>(tag_invoke, set_stopped, R)</del><ins><em>MANDATE-NOTHROW</em>(R.set_stopped())</ins>.
</pre></blockquote>
</p>
<p>In [exec.opstate]/1, edit as follows:
<blockquote><pre><ins>template<class O>
inline constexpr bool <em>enable-operation-state</em> = <em>// exposition only</em>
requires {
requires derived_from<typename O::operation_state_concept, operation_state_t>;
};</ins>
template<class O>
concept operation_state =
<ins><em>enable-operation-state</em><O> &&</ins>
queryable<O> &&
is_object_v<O> &&
requires (O& o) {
{ <ins>execution::</ins>start(o) } noexcept;
};
</pre></blockquote>
</p>
<p>In [exec.snd.concepts]/1, edit as follows:
<blockquote><pre>template<class Sndr>
inline constexpr bool <del>enable_sender</del><ins><em>enable-sender</em></ins> = <ins><em>// exposition only</em> </ins>
requires {
requires derived_from<typename Sndr::sender_concept, sender_t>;
};
template<is-awaitable<<em>env-promise</em><empty_env>> Sndr> <em>// [exec.awaitables]</em>
inline constexpr bool <del>enable_sender</del><ins><em>enable-sender</em></ins><Sndr> = true;
template<class Sndr>
concept sender =
<del>enable_sender</del><ins><em>enable-sender</em></ins><remove_cvref_t<Sndr>> &&
requires (const remove_cvref_t<Sndr>& sndr) {
{ <ins>execution::</ins>get_env(sndr) } -> queryable;
} &&
move_constructible<remove_cvref_t<Sndr>> && <em>// rvalues are movable, and</em>
constructible_from<remove_cvref_t<Sndr>, Sndr>; <em>// lvalues are copyable</em>
template<class Sndr, class Env = empty_env>
concept sender_in =
sender<Sndr> &&
requires (Sndr&& sndr, Env&& env) {
{ <ins>execution::</ins>get_completion_signatures(std::forward<Sndr>(sndr), std::forward<Env>(env)) } ->
<em>valid-completion-signatures</em>;
};
template<class Sndr, class Rcvr>
concept sender_to =
sender_in<Sndr, env_of_t<Rcvr>> &&
receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>> &&
requires (Sndr&& sndr, Rcvr&& rcvr) {
<ins>execution::</ins>connect(std::forward<Sndr>(sndr), std::forward<Rcvr>(rcvr));
};
</pre></blockquote>
</p>
<p>Strike [exec.snd.concepts]/3:
<blockquote><pre><del>Remarks: Pursuant to [namespace.std], users can specialize enable_sender to true for <em>cv</em>-unqualified program-defined
types that model sender, and false for types that do not. Such specializations shall be usable in constant expressions ([expr.const]) and have type const bool.</del>
</pre></blockquote>
</p>
<p>In [exec.snd.concepts]/6, edit as follows:
<blockquote><pre>
Library-provided sender types:
- Always expose an overload of a <del>customization of</del><ins>member</ins> connect that accepts an rvalue sender.
- Only expose an overload of a <del>customization of</del><ins>member</ins> connect that accepts an lvalue sender if they model copy_constructible.
- Model copy_constructible if they satisfy copy_constructible.
</pre></blockquote>
</p>
<p>In [exec.awaitables]/5, edit as follows:
<blockquote><pre>
<ins>template<class T, class Promise>
concept <em>has-as-awaitable</em> = requires (T&& t, Promise& p) {
{ std::forward<T>(t).as_awaitable(p) } -> <em>is-awaitable</em><Promise&>;
};</ins>
</ins>
template<<del>class</del><ins><em>has-as-awaitable</em><Derived></ins> T>
<del>requires tag_invocable<as_awaitable_t, T, Derived&></del>
auto await_transform(T&& value) <ins>noexcept(noexcept(std::forward<T>(value).as_awaitable(declval<Derived&>())))</ins>
<del>noexcept(nothrow_tag_invocable<as_awaitable_t, T, Derived&>)</del>
-> <del>tag_invoke_result_t<as_awaitable_t, T, Derived&></del> <ins>decltype(std::forward<T>(value).as_awaitable(declval<Derived&>()))</ins> {
return <del>tag_invoke(as_awaitable,</del> std::forward<T>(value)<del>,</del><ins>.as_awaitable(</ins>static_cast<Derived&>(*this));
}
</pre></blockquote>
</p>
<p>In [exec.awaitables]/6, edit as follows:
<blockquote><pre>template<class Env>
struct <em>env-promise</em> : <em>with-await-transform</em><<em>env-promise</em><Env>> {
<em>unspecified</em> get_return_object() noexcept;
<em>unspecified</em> initial_suspend() noexcept;
<em>unspecified</em> final_suspend() noexcept;
void unhandled_exception() noexcept;
void return_void() noexcept;
coroutine_handle<> unhandled_stopped() noexcept;
<del>friend const Env& tag_invoke(get_env_t, const <em>env-promise</em>&) noexcept;</del>
<ins>const Env& get_env() const noexcept;</ins>
};
</pre></blockquote>
</p>
<p>In [exec.getcomplsigs]/1, edit as follows:
<blockquote><pre><ins>execution::</ins>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
<ins>execution::</ins>get_completion_signatures(s, e) is expression-equivalent to:
<ol><li><del>tag_invoke_result_t<get_completion_signatures_t, S, E>{}</del><ins>decltype(s.get_completion_signatures(e)){}</ins>
if that expression is well-formed,</li>
<li> ... </li></ol>
</pre></blockquote>
</p>
<p>In [exec.connect]/3, edit as follows:
<blockquote><pre>Let <em>connect-awaitable-promise</em> be the following class:
struct <em>connect-awaitable-promise</em> : <em>with-await-transform</em><<em>connect-awaitable-promise</em>> {
DR& rcvr; <em>// exposition only</em>
<em>connect-awaitable-promise</em>(DS&, DR& r) noexcept : rcvr(r) {}
suspend_always initial_suspend() noexcept { return {}; }
[[noreturn]] suspend_always final_suspend() noexcept { std::terminate(); }
[[noreturn]] void unhandled_exception() noexcept { std::terminate(); }
[[noreturn]] void return_void() noexcept { std::terminate(); }
coroutine_handle<> unhandled_stopped() noexcept {
<ins>execution::</ins>set_stopped((DR&&) rcvr);
return noop_coroutine();
}
<em>operation-state-task</em> get_return_object() noexcept {
return <em>operation-state-task</em>{
coroutine_handle<<em>connect-awaitable-promise</em>>::from_promise(*this)};
}
<del>friend</del> env_of_t<const DR&> <del>tag_invoke</del><ins>get_env</ins>(<del>get_env_t, const <em>connect-awaitable-promise</em>& self</del>) <ins>const</ins> noexcept {
return <ins>execution::</ins>get_env(<del>self.</del>rcvr);
}
};
</pre></blockquote>
</p>
<p>In [exec.connect]/4, edit as follows:
<blockquote><pre>Let <em>operation-state-task</em> be the following class:
struct <em>operation-state-task</em> {
using promise_type = <em>connect-awaitable-promise</em>;
coroutine_handle<> coro; <em>// exposition only</em>
explicit <em>operation-state-task</em>(coroutine_handle<> h) noexcept : coro(h) {}
<em>operation-state-task</em>(<em>operation-state-task</em>&& o) noexcept
: coro(exchange(o.coro, {})) {}
~<em>operation-state-task</em>() { if (coro) coro.destroy(); }
<del>friend</del> void <del>tag_invoke(start_t, </del><ins>start(</ins><del><em>operation-state-task</em>& self</del>) & noexcept {
<del>self.</del>coro.resume();
}
};
</pre></blockquote>
</p>
<p>In [exec.connect]/6, edit as follows:
<blockquote><pre>If S does not satisfy sender or if R does not satisfy receiver, <ins>execution::</ins>connect(s, r)
is ill-formed. Otherwise, the expression <ins>execution::</ins>connect(s, r) is expression-equivalent to:
<ol><li><del>tag_invoke(connect, s, r)</del><ins>s.connect(r)</ins> <del>if connectable-with-tag-invoke<S, R>
is modeled</del><ins>if that expression is well-formed</ins>.
<em>Mandates:</em> The type of the <del>tag_invoke</del> expression above satisfies operation_state.</li>
<li>Otherwise, <em>connect-awaitable</em>(s, r) if that expression is well-formed.</li>
<li>Otherwise, <ins>execution::</ins>connect(s, r) is ill-formed.</li></ol></pre></blockquote>
</p>
<p>In [exec.adapt.general]3,4,5, edit as follows:
<blockquote><pre>Unless otherwise specified, a sender adaptor is required to not begin executing any functions that would
observe or modify any of the arguments of the adaptor before the returned sender
is connected with a receiver using <ins>execution::</ins>connect, and <ins>execution::</ins>start is called on the
resulting operation state. This requirement applies to any function that
is selected by the implementation of the sender adaptor.
Unless otherwise specified, a parent sender ([async.ops]) with a single child
sender s has an associated attribute object equal to
<em>FWD-QUERIES</em>(<ins>execution::</ins>get_env(s)) ([exec.fwd.env]).
Unless otherwise specified, a parent sender with more than one child senders
has an associated attributes object equal to empty_env{}.
These requirements apply to any function that is selected by the implementation of the sender adaptor.
Unless otherwise specified, when a parent sender is connected to a receiver r,
any receiver used to connect a child sender has an associated environment
equal to <em>FWD-QUERIES</em>(<ins>execution::</ins>get_env(r)). This requirements applies
to any sender returned from a function that is selected by the implementation of such sender adaptor.
</pre></blockquote>
</p>
<p>Strike [exec.adapt.general]6:
<blockquote><pre><del>For any sender type, receiver type, operation state type, queryable type, or coroutine promise type that is part
of the implementation of any sender adaptor in this subclause and that is
a class template, the template arguments do not contribute to the associated
entities ([basic.lookup.argdep]) of a function call where a specialization
of the class template is an associated entity.
[Example:...</del>
</pre></blockquote>
</p>
<p>
For the sender adapters, the changes from P2999 regarding how algorithms are
customized already removes tag_invokes, so no further changes are needed to
them.
</p>
<p>Change [exec.as.awaitable]2 as follows:</p>
<blockquote><pre>as_awaitable is a customization point object. For some subexpressions expr and p
where p is an lvalue, Expr names the type decltype((expr)) and Promise names the type decltype((p)),
as_awaitable(expr, p) is expression-equivalent to the following:
<del>tag_invoke(as_awaitable, expr, p)</del><ins>expr.as_awaitable(p)</ins> if that expression is well-formed.
<em>Mandates:</em> <em>is-awaitable</em><A, Promise> is true, where A is the type of the <del>tag_invoke</del> expression above.
</pre></blockquote>
</body>
</html>