@@ -648,18 +648,16 @@ implementations.
648
648
```c++
649
649
namespace stdexec = std::execution;
650
650
651
- template<class R, class F>
652
- class _then_receiver
653
- : public stdexec::receiver_adaptor<_then_receiver<R, F>, R> {
651
+ template <class R, class F>
652
+ class _then_receiver : public R {
654
653
F f_;
655
654
656
655
public:
657
- _then_receiver(R r, F f)
658
- : stdexec::receiver_adaptor<_then_receiver, R>{std::move(r)}
659
- , f_(std::move(f)) {}
656
+ _then_receiver(R r, F f) : R(std::move(r)), f_(std::move(f)) {}
660
657
661
- // Customize set_value by invoking the callable and passing the result to the inner receiver
662
- template<class... As>
658
+ // Customize set_value by invoking the callable and passing the result to
659
+ // the inner receiver
660
+ template <class... As>
663
661
requires std::invocable<F, As...>
664
662
void set_value(As&&... as) && noexcept {
665
663
try {
@@ -670,7 +668,7 @@ class _then_receiver
670
668
}
671
669
};
672
670
673
- template<stdexec::sender S, class F>
671
+ template <stdexec::sender S, class F>
674
672
struct _then_sender {
675
673
using sender_concept = stdexec::sender_t;
676
674
S s_;
@@ -680,28 +678,30 @@ struct _then_sender {
680
678
using _set_value_t = stdexec::completion_signatures<
681
679
stdexec::set_value_t(std::invoke_result_t<F, Args...>)>;
682
680
681
+ using _except_ptr_sig =
682
+ stdexec::completion_signatures<stdexec::set_error_t(std::exception_ptr)> ;
683
+
683
684
// Compute the completion signatures
684
- template<class Env>
685
+ template <class Env>
685
686
auto get_completion_signatures(Env&& env) && noexcept
686
- -> stdexec::transform_completion_signatures_of<S, Env,
687
- stdexec::completion_signatures<stdexec::set_error_t(std::exception_ptr)> ,
688
- _set_value_t> {
687
+ -> stdexec::transform_completion_signatures_of<
688
+ S, Env, _except_ptr_sig, _set_value_t> {
689
689
return {};
690
690
}
691
691
692
692
// Connect:
693
- template<stdexec::receiver R>
693
+ template <stdexec::receiver R>
694
694
auto connect(R r) && -> stdexec::connect_result_t<S, _then_receiver<R, F>> {
695
- return stdexec::connect(
696
- (S&&) s_, _then_receiver<R, F> {(R&&) r, (F&&) f_});
695
+ return stdexec::connect(
696
+ (S&&) s_, _then_receiver{(R&&) r, (F&&) f_});
697
697
}
698
698
699
699
decltype(auto) get_env() const noexcept {
700
700
return get_env(s_);
701
701
}
702
702
};
703
703
704
- template<stdexec::sender S, class F>
704
+ template <stdexec::sender S, class F>
705
705
stdexec::sender auto then(S s, F f) {
706
706
return _then_sender<S, F>{(S&&) s, (F&&) f};
707
707
}
@@ -714,15 +714,11 @@ well as all receiver queries, are passed through unchanged.
714
714
715
715
In detail, it does the following:
716
716
717
- 1. Defines a receiver in terms of `execution::receiver_adaptor` that aggregates
718
- another receiver and an invocable that:
717
+ 1. Defines a receiver in terms of receiver and an invocable that:
719
718
* Defines a constrained `set_value` member function for transforming the
720
719
value channel.
721
720
* Delegates `set_error` and `set_stopped` to the inner receiver.
722
721
723
- The `set_error` and `set_stopped` member functions are provided by
724
- `receiver_adaptor`.
725
-
726
722
2. Defines a sender that aggregates another sender and the invocable, which
727
723
defines a `connect` member function that wraps the incoming receiver in the
728
724
receiver from (1) and passes it and the incoming sender to
@@ -742,10 +738,12 @@ concept _decays_to = same_as<decay_t<From>, To>;
742
738
// _conv needed so we can emplace construct non-movable types into
743
739
// a std::optional.
744
740
template<invocable F>
745
- requires is_nothrow_move_constructible_v<F>
746
741
struct _conv {
747
742
F f_;
743
+
744
+ static_assert(is_nothrow_move_constructible_v<F>);
748
745
explicit _conv(F f) noexcept : f_((F&&) f) {}
746
+
749
747
operator invoke_result_t<F>() && {
750
748
return ((F&&) f_)();
751
749
}
@@ -754,20 +752,27 @@ struct _conv {
754
752
template<class S, class R>
755
753
struct _retry_op;
756
754
757
- // pass through all customizations except set_error, which retries the operation.
755
+ // pass through all customizations except set_error, which retries
756
+ // the operation.
758
757
template<class S, class R>
759
- struct _retry_receiver
760
- : stdexec::receiver_adaptor<_retry_receiver<S, R>> {
758
+ struct _retry_receiver {
761
759
_retry_op<S, R>* o_;
762
760
763
- R&& base() && noexcept { return std::move(o_->r_); }
764
- const R& base() const & noexcept { return o_->r_; }
765
-
766
- explicit _retry_receiver(_retry_op<S, R>* o) : o_(o) {}
761
+ void set_value(auto&&... as) && noexcept {
762
+ stdexec::set_value(std::move(o_->r_), (decltype(as)&&) as...);
763
+ }
767
764
768
765
void set_error(auto&&) && noexcept {
769
766
o_->_retry(); // This causes the op to be retried
770
767
}
768
+
769
+ void set_stopped() && noexcept {
770
+ stdexec::set_stopped(std::move(o_->r_));
771
+ }
772
+
773
+ decltype(auto) get_env() const noexcept {
774
+ return get_env(o_->r_);
775
+ }
771
776
};
772
777
773
778
// Hold the nested operation state in an optional so we can
@@ -1213,8 +1218,6 @@ that the completion-signal of will be transferred to the given context.
1213
1218
10. Some additional utilities are added:
1214
1219
* <b> `run_loop`</b> : An execution resource that provides a multi-producer,
1215
1220
single-consumer, first-in-first-out work queue.
1216
- * <b> `receiver_adaptor`</b> : A utility for algorithm authors for defining one
1217
- receiver type in terms of another.
1218
1221
* <b> `completion_signatures`</b> and <b> `transform_completion_signatures`</b> :
1219
1222
Utilities for describing the ways in which a sender can complete in a
1220
1223
declarative syntax.
@@ -1461,8 +1464,11 @@ The changes since R8 are as follows:
1461
1464
* The `tag_invoke` mechanism has been replace with member functions
1462
1465
for customizations as per \[P2855] (https://wg21.link/p2855).
1463
1466
1464
- * The removal of `tag_invoke` necessitated a respecification of the
1465
- `receiver_adaptor` utility.
1467
+ * Per guidance from LWG and LEWG, `receiver_adaptor` has been removed.
1468
+
1469
+ * The `receiver` concept is tweaked to requires that receiver types are not
1470
+ `final`. Without `receiver_adaptor` and `tag_invoke`, receiver adaptors
1471
+ are easily written using implementation inheritance.
1466
1472
1467
1473
<b> Enhancements:</b>
1468
1474
@@ -5023,10 +5029,6 @@ namespace std::execution {
5023
5029
inline constexpr start_detached_t start_detached{};
5024
5030
5025
5031
// [exec.utils] , sender and receiver utilities
5026
- // [exec.utils.rcvr.adptr]
5027
- template<<i> class-type</i> Derived, receiver Base = <i> unspecified</i> >
5028
- class receiver_adaptor;
5029
-
5030
5032
// [exec.utils.cmplsigs]
5031
5033
template<class Fn>
5032
5034
concept <i> completion-signature</i> = <i> // exposition only</i>
@@ -5425,6 +5427,8 @@ enum class forward_progress_guarantee {
5425
5427
receiver<Rcvr> && <i> has-completions</i> <Rcvr, Completions>;
5426
5428
</pre>
5427
5429
5430
+ 2. Class types that are `final` do not model the `receiver` concept.
5431
+
5428
5432
3. Let `rcvr` be a receiver and let `op_state` be an operation state associated
5429
5433
with an asynchronous operation created by connecting `rcvr` with a sender. Let
5430
5434
`token` be a stop token equal to `get_stop_token(get_env(rcvr))`. `token` shall
@@ -6848,7 +6852,8 @@ template<class Domain, class Tag, sender Sndr, class... Args>
6848
6852
3. Let <i> `receiver-type`</i> denote the following class:
6849
6853
6850
6854
<pre highlight="c++">
6851
- struct <i> receiver-type</i> : receiver_adaptor<<i> receiver-type</i> > {
6855
+ struct <i> receiver-type</i> {
6856
+ using receiver_concept = receiver_t;
6852
6857
<i> state-type</i> * <i> state</i> ; // exposition only
6853
6858
6854
6859
Rcvr&& base() && noexcept { return std::move(<i> state</i> -><i> rcvr</i> ); }
@@ -6864,6 +6869,19 @@ template<class Domain, class Tag, sender Sndr, class... Args>
6864
6869
},
6865
6870
<i> state</i> -><i> async-result</i> );
6866
6871
}
6872
+
6873
+ template<class Error>
6874
+ void set_error(Error&& err) && noexcept {
6875
+ execution::set_error(std::move(<i> state</i> -><i> rcvr</i> ), std::forward<Error>(err));
6876
+ }
6877
+
6878
+ void set_stopped() && noexcept {
6879
+ execution::set_stopped(std::move(<i> state</i> -><i> rcvr</i> ));
6880
+ }
6881
+
6882
+ decltype(auto) get_env() const noexcept {
6883
+ return <i> FWD-ENV</i> (execution::get_env(<i> state</i> -><i> rcvr</i> ));
6884
+ }
6867
6885
};
6868
6886
</pre>
6869
6887
@@ -7010,12 +7028,13 @@ template<class Domain, class Tag, sender Sndr, class... Args>
7010
7028
7011
7029
<pre highlight="c++">
7012
7030
template<class Rcvr, class Env>
7013
- struct <i> receiver2</i> : receiver_adaptor< <i> receiver2 </i> < Rcvr, Env>, Rcvr> {
7031
+ struct <i> receiver2</i> : Rcvr {
7014
7032
explicit <i> receiver2</i> (Rcvr rcvr, Env env)
7015
- : <i> receiver2 </i> ::receiver_adaptor{ std::move(rcvr)} , env(std::move(env)) {}
7033
+ : Rcvr( std::move(rcvr)) , env(std::move(env)) {}
7016
7034
7017
7035
auto get_env() const noexcept {
7018
- return <i> JOIN-ENV</i> (env, <i> FWD-ENV</i> (execution::get_env(this->base())));
7036
+ const Rcvr& rcvr = *this;
7037
+ return <i> JOIN-ENV</i> (env, <i> FWD-ENV</i> (execution::get_env(rcvr)));
7019
7038
}
7020
7039
7021
7040
Env env; // exposition only
@@ -8238,147 +8257,6 @@ template<class Domain, class Tag, sender Sndr, class... Args>
8238
8257
8239
8258
## Sender/receiver utilities <b> [exec.utils] </b> ## {#spec-execution.snd_rec_utils}
8240
8259
8241
- ### `execution::receiver_adaptor` <b> [exec.utils.rcvr.adptr] </b> ### {#spec-execution.snd_rec_utils.rcvr_adptr}
8242
-
8243
- <pre highlight="c++">
8244
- template<<i> class-type</i> Derived, receiver Base = <i> unspecified</i> >
8245
- class receiver_adaptor;
8246
- </pre>
8247
-
8248
- 1. `receiver_adaptor` simplifies the implementation of one receiver type in
8249
- terms of another. It defines named member functions that forward to
8250
- identically named members in the derived type if they exist, and to the
8251
- adapted receiver otherwise.
8252
-
8253
- 2. Let <code><i> HAS-BASE</i></code> be `false` if `Base` is an alias for the
8254
- unspecified default template argument; otherwise, it is `true`.
8255
-
8256
- 3. `receiver_adaptor<Derived, Base>` is as follows:
8257
-
8258
- <pre highlight="c++">
8259
- template<<i> class-type</i> Derived, receiver Base = <i> unspecified</i> >
8260
- class receiver_adaptor {
8261
- public:
8262
- using receiver_concept = receiver_t;
8263
-
8264
- // Constructors
8265
- receiver_adaptor() = default;
8266
- template<class B>
8267
- requires <i> HAS-BASE</i> && constructible_from<Base, B>
8268
- explicit receiver_adaptor(B&& b) : <i> base-rcvr</i> (std::forward<B>(b)) {}
8269
-
8270
- // Member functions
8271
- template<class... Args>
8272
- void set_value(Args&&... args) && noexcept;
8273
-
8274
- template<class Error>
8275
- void set_error(Error&& err) && noexcept;
8276
-
8277
- void set_stopped() && noexcept;
8278
-
8279
- decltype(auto) get_env() const noexcept {
8280
- return execution::get_env(<i> get-base</i> ());
8281
- }
8282
-
8283
- private:
8284
- friend Derived;
8285
-
8286
- // Private member functions
8287
- template<class Self>
8288
- decltype(auto) <i> get-base</i> (this Self&& self) noexcept; <i> // exposition only</i>
8289
-
8290
- template<class Self>
8291
- decltype(auto) base(this Self&& self) noexcept requires <i> HAS-BASE</i> ;
8292
-
8293
- Base <i> base-rcvr</i> ; // exposition only, present if and only if <i> HAS-BASE</i> is true
8294
- };
8295
- </pre>
8296
-
8297
- 4. [<i> Note:</i> The `Derived` template parameter denotes a class type that is
8298
- incomplete when `receiver_adaptor` is instantiated.]
8299
-
8300
- 5. [<i> Example:</i>
8301
- <pre highlight="c++">
8302
- using _int_completion =
8303
- completion_signatures<set_value_t(int)>;
8304
-
8305
- template<receiver_of<_int_completion> Rcvr>
8306
- struct my_receiver
8307
- : receiver_adaptor<my_receiver<Rcvr>, Rcvr> {
8308
- explicit my_receiver(Rcvr r)
8309
- : receiver_adaptor<my_receiver, Rcvr>(std::move(r)) {}
8310
-
8311
- void set_value() && noexcept {
8312
- std::execution::set_value(std::move(*this).base(), 42);
8313
- }
8314
- };
8315
- </pre>
8316
- -- <i> end example</i> ]
8317
-
8318
- #### Member functions <b> [exec.utils.rcvr.adptr.members] </b> #### {#spec-execution.snd_rec_utils.receiver_adaptor.members}
8319
-
8320
- <pre highlight="c++">
8321
- template<class... Args>
8322
- void set_value(Args&&... args) && noexcept;
8323
- </pre>
8324
-
8325
- 1. Let <i> `e`</i> be the expression
8326
- <code> execution::set_value(std::move(<i> get-base</i> ()), std::forward<Args>(args)...)</code> .
8327
-
8328
- 2. <i> Constraints:</i> <i> `e`</i> is a well-formed expression.
8329
-
8330
- 3. <i> Effects:</i> Equivalent to <i> `e`</i> .
8331
-
8332
- <pre highlight="c++">
8333
- template<class Error>
8334
- void set_error(Error&& err) && noexcept;
8335
- </pre>
8336
-
8337
- 1. Let <i> `e`</i> be the expression
8338
- <code> execution::set_error(std::move(<i> get-base</i> ()), std::forward<Error>(err))</code> .
8339
-
8340
- 2. <i> Constraints:</i> <i> `e`</i> is a well-formed expression.
8341
-
8342
- 3. <i> Effects:</i> Equivalent to <i> `e`</i> .
8343
-
8344
- <pre highlight="c++">
8345
- void set_stopped() && noexcept;
8346
- </pre>
8347
-
8348
- 1. Let <i> `e`</i> be the expression
8349
- <code> execution::set_stopped(std::move(<i> get-base</i> ()))</code> .
8350
-
8351
- 2. <i> Constraints:</i> <i> `e`</i> is a well-formed expression.
8352
-
8353
- 3. <i> Effects:</i> Equivalent to <i> `e`</i> .
8354
-
8355
- <pre highlight="c++">
8356
- template<class Self>
8357
- decltype(auto) <i> get-base</i> (this Self&& self) noexcept;
8358
- </pre>
8359
-
8360
- 1. Let <i> `e`</i> be the expression
8361
- <code> std::forward_like<Self>((Derived&) self).base()</code> if that expression
8362
- is well-formed; otherwise, it is
8363
- <code> std::forward_like<Self>((receiver_adaptor&) self).base()</code> .
8364
- <span class="wg21note"> The C-style casts are to disable accessibility checks.</span>
8365
-
8366
- 2. <i> Mandates:</i> `is_base_of_v<receiver_adaptor, Derived> ` is `true` and <i> `e`</i>
8367
- is a well-formed expression.
8368
-
8369
- 3. <i> Returns:</i> <i> `e`</i> .
8370
-
8371
- <pre highlight="c++">
8372
- template<class Self>
8373
- decltype(auto) base(this Self&& self) noexcept requires <i> HAS-BASE</i> ;
8374
- </pre>
8375
-
8376
- 1. Let <i> `e`</i> be the expression
8377
- <code> std::forward_like<Self>((receiver_adaptor&) self).<i> base-rcvr</i></code> .
8378
- <span class="wg21note"> The C-style cast is to disable accessibility checks.</span>
8379
-
8380
- 3. <i> Returns:</i> <code> (<i> e</i> )</code> .
8381
-
8382
8260
### `execution::completion_signatures` <b> [exec.utils.cmplsigs] </b> ### {#spec-execution.snd_rec_utils.completion_sigs}
8383
8261
8384
8262
1. `completion_signatures` is a type that encodes a set of completion signatures
0 commit comments