Skip to content

Commit 06dacf5

Browse files
committed
basic: add Sender, Receives and Operation concepts
1 parent d5f9f08 commit 06dacf5

File tree

6 files changed

+205
-20
lines changed

6 files changed

+205
-20
lines changed

docs/src/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
- [detached](headers/basic/detached.md)
2323
- [spawn](headers/basic/spawn.md)
2424
- [Waitable](headers/basic/waitable.md)
25+
- [Sender](headers/basic/sender.md)
26+
- [Operation](headers/basic/operation.md)
27+
- [Receives<T>](headers/basic/receives.md)
2528
- [async/result.hpp](headers/result.md)
2629
- [async/oneshot.hpp](headers/oneshot-event.md)
2730
- [async/wait-group.hpp](headers/wait-group.md)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# `concept Operation`
2+
3+
The `Operation` concept holds all the requirements for an
4+
[operation](/sender-receiver.md).
5+
6+
## Prototype
7+
8+
```cpp
9+
template<typename T>
10+
concept Operation = ...;
11+
```
12+
13+
### Requirements
14+
15+
`T` can be started via the [start\_inline](/headers/execution.md) CPO.
16+
17+
## Examples
18+
19+
```cpp
20+
template <typename Receiver>
21+
struct write_operation {
22+
write_operation(write_sender s, Receiver r)
23+
: req_{}, handle_{s.handle}, bufs_{s.bufs}, nbufs_{s.nbufs}, r_{std::move(r)} { }
24+
25+
write_operation(const write_operation &) = delete;
26+
write_operation &operator=(const write_operation &) = delete;
27+
write_operation(write_operation &&) = delete;
28+
write_operation &operator=(write_operation &&) = delete;
29+
30+
bool start_inline() { /* omitted for brevity */ }
31+
32+
private:
33+
uv_write_t req_;
34+
uv_stream_t *handle_;
35+
const uv_buf_t *bufs_;
36+
size_t nbufs_;
37+
38+
Receiver r_;
39+
};
40+
static_assert(async::Operation<write_operation<noop_receiver>>);
41+
```

docs/src/headers/basic/receives.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# `concept Receives`
2+
3+
The `Receives<T>` concept holds all the requirements for a
4+
[receiver](/sender-receiver.md) that can receive a `T` value (or none, when `T`
5+
is `void`).
6+
7+
## Prototype
8+
9+
```cpp
10+
template<typename T, typename Receives>
11+
concept Receives = ...;
12+
```
13+
14+
### Requirements
15+
16+
A `set_value_inline` and `set_value_noinline` members, which can be called with
17+
a `T&&` value, or no parameters, if `T` is `void`.
18+
19+
## Examples
20+
21+
```cpp
22+
struct discard_receiver {
23+
template<typename T>
24+
void set_value_inline(T) {
25+
assert(std::is_constant_evaluated());
26+
}
27+
void set_value_inline() {
28+
assert(std::is_constant_evaluated());
29+
}
30+
31+
template<typename T>
32+
void set_value_noinline(T) {
33+
assert(std::is_constant_evaluated());
34+
}
35+
void set_value_noinline() {
36+
assert(std::is_constant_evaluated());
37+
}
38+
};
39+
static_assert(async::Receives<discard_receiver, void>);
40+
static_assert(async::Receives<discard_receiver, int>);
41+
static_assert(async::Receives<discard_receiver, std::string>);
42+
```

docs/src/headers/basic/sender.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# `concept Sender`
2+
3+
The `Sender` concept holds all the requirements for a
4+
[sender](/sender-receiver.md).
5+
6+
## Prototype
7+
8+
```cpp
9+
template<typename T>
10+
concept Sender = ...;
11+
```
12+
13+
### Requirements
14+
15+
`T` has a `value_type`, is move constructible, and can be
16+
[connected](/headers/execution.md).
17+
18+
## Examples
19+
20+
```cpp
21+
struct [[nodiscard]] write_sender {
22+
using value_type = int; // Status code
23+
24+
uv_stream_t *handle;
25+
const uv_buf_t *bufs;
26+
size_t nbufs;
27+
};
28+
29+
/* operation omitted for brevity */
30+
template <typename Receiver>
31+
/*operation*/<Receiver> connect(write_sender s, Receiver r) {
32+
return {s, std::move(r)};
33+
}
34+
static_assert(async::Sender<write_sender>);
35+
```

include/async/algorithm.hpp

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace async {
1212

13-
template<typename Sender, typename Receiver>
13+
template<Sender Sender, Receives<typename Sender::value_type> Receiver>
1414
struct connect_helper {
1515
using operation = execution::operation_t<Sender, Receiver>;
1616

@@ -22,7 +22,7 @@ struct connect_helper {
2222
Receiver r;
2323
};
2424

25-
template<typename Sender, typename Receiver>
25+
template<Sender Sender, Receives<typename Sender::value_type> Receiver>
2626
connect_helper<Sender, Receiver> make_connect_helper(Sender s, Receiver r) {
2727
return {std::move(s), std::move(r)};
2828
}
@@ -59,7 +59,7 @@ template<typename F>
5959
struct [[nodiscard]] invocable_sender {
6060
using value_type = std::invoke_result_t<F>;
6161

62-
template<typename R>
62+
template<Receives<value_type> R>
6363
invocable_operation<F, R> connect(R r) {
6464
return {std::move(f), std::move(r)};
6565
}
@@ -165,7 +165,7 @@ requires std::same_as<typename Sender::value_type, void>
165165
struct [[nodiscard]] transform_sender<Sender, F> {
166166
using value_type = std::invoke_result_t<F>;
167167

168-
template<typename Receiver>
168+
template<Receives<value_type> Receiver>
169169
friend auto connect(transform_sender s, Receiver dr) {
170170
return execution::connect(std::move(s.ds),
171171
void_transform_receiver<Receiver, F>{std::move(dr), std::move(s.f)});
@@ -234,7 +234,7 @@ struct [[nodiscard]] ite_sender {
234234
ite_sender(C cond, ST then_s, SE else_s)
235235
: cond_{std::move(cond)}, then_s_{std::move(then_s)}, else_s_{std::move(else_s)} { }
236236

237-
template<typename R>
237+
template<Receives<value_type> R>
238238
ite_operation<C, ST, SE, R> connect(R dr) {
239239
return {std::move(cond_), std::move(then_s_), std::move(else_s_), std::move(dr)};
240240
}
@@ -249,7 +249,8 @@ struct [[nodiscard]] ite_sender {
249249
SE else_s_;
250250
};
251251

252-
template<typename C, typename ST, typename SE>
252+
template<std::invocable<> C, Sender ST, Sender SE>
253+
requires std::same_as<typename ST::value_type, typename SE::value_type>
253254
ite_sender<C, ST, SE> ite(C cond, ST then_s, SE else_s) {
254255
return {std::move(cond), std::move(then_s), std::move(else_s)};
255256
}
@@ -326,7 +327,7 @@ struct repeat_while_sender {
326327
return {std::move(*this)};
327328
}
328329

329-
template<typename R>
330+
template<Receives<value_type> R>
330331
repeat_while_operation<C, SF, R> connect(R receiver) {
331332
return {std::move(cond), std::move(factory), std::move(receiver)};
332333
}
@@ -336,6 +337,10 @@ struct repeat_while_sender {
336337
};
337338

338339
template<typename C, typename SF>
340+
requires std::move_constructible<C> && requires (C c, SF sf) {
341+
{ c() } -> std::convertible_to<bool>;
342+
{ sf() } -> Sender;
343+
}
339344
repeat_while_sender<C, SF> repeat_while(C cond, SF factory) {
340345
return {std::move(cond), std::move(factory)};
341346
}
@@ -351,7 +356,7 @@ template<typename... Functors>
351356
struct race_and_cancel_sender {
352357
using value_type = void;
353358

354-
template<typename Receiver>
359+
template<Receives<value_type> Receiver>
355360
friend race_and_cancel_operation<Receiver, frg::tuple<Functors...>,
356361
std::index_sequence_for<Functors...>>
357362
connect(race_and_cancel_sender s, Receiver r) {
@@ -450,7 +455,8 @@ operator co_await(race_and_cancel_sender<Functors...> s) {
450455
return {std::move(s)};
451456
}
452457

453-
template<typename... Functors>
458+
template<std::invocable<cancellation_token>... Functors>
459+
requires ((Sender<std::invoke_result_t<Functors, cancellation_token>>) && ...)
454460
race_and_cancel_sender<Functors...> race_and_cancel(Functors... fs) {
455461
return {{fs...}};
456462
}
@@ -495,7 +501,7 @@ struct [[nodiscard]] let_sender {
495501
using imm_type = std::invoke_result_t<Pred>;
496502
using value_type = typename std::invoke_result_t<Func, std::add_lvalue_reference_t<imm_type>>::value_type;
497503

498-
template<typename Receiver>
504+
template<Receives<value_type> Receiver>
499505
friend let_operation<Receiver, Pred, Func>
500506
connect(let_sender s, Receiver r) {
501507
return {std::move(s.pred), std::move(s.func), std::move(r)};
@@ -511,7 +517,10 @@ operator co_await(let_sender<Pred, Func> s) {
511517
return {std::move(s)};
512518
}
513519

514-
template <typename Pred, typename Func>
520+
template <std::invocable<> Pred, typename Func>
521+
requires requires (Func func, Pred pred) {
522+
func(std::declval<std::add_lvalue_reference_t<decltype(pred())>>());
523+
}
515524
let_sender<Pred, Func> let(Pred pred, Func func) {
516525
return {std::move(pred), std::move(func)};
517526
}
@@ -703,7 +712,7 @@ sequence_sender<Senders...> sequence(Senders ...senders) {
703712
return {frg::tuple<Senders...>{std::move(senders)...}};
704713
}
705714

706-
template <typename ...Senders>
715+
template <Sender ...Senders>
707716
sender_awaiter<sequence_sender<Senders...>, typename sequence_sender<Senders...>::value_type>
708717
operator co_await(sequence_sender<Senders...> s) {
709718
return {std::move(s)};
@@ -778,7 +787,7 @@ template <typename ...Senders> requires (sizeof...(Senders) > 0)
778787
struct [[nodiscard]] when_all_sender {
779788
using value_type = void;
780789

781-
template<typename Receiver>
790+
template<Receives<value_type> Receiver>
782791
friend when_all_operation<Receiver, Senders...>
783792
connect(when_all_sender s, Receiver r) {
784793
return {std::move(s.senders), std::move(r)};
@@ -787,7 +796,8 @@ struct [[nodiscard]] when_all_sender {
787796
frg::tuple<Senders...> senders;
788797
};
789798

790-
template <typename ...Senders> requires (sizeof...(Senders) > 0)
799+
template <Sender ...Senders>
800+
requires (sizeof...(Senders) > 0)
791801
when_all_sender<Senders...> when_all(Senders ...senders) {
792802
return {frg::tuple<Senders...>{std::move(senders)...}};
793803
}

include/async/basic.hpp

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,57 @@ namespace corons = std::experimental;
3737
#endif
3838

3939
namespace async {
40+
template<typename T, typename Value>
41+
concept Receives = std::movable<T>
42+
&& (std::same_as<Value, void> ?
43+
requires(T t) {
44+
{ t.set_value_inline() } -> std::same_as<void>;
45+
{ t.set_value_noinline() } -> std::same_as<void>;
46+
}
47+
: requires(T t) {
48+
{ t.set_value_inline(std::declval<Value>()) } -> std::same_as<void>;
49+
{ t.set_value_noinline(std::declval<Value>()) } -> std::same_as<void>;
50+
});
51+
52+
namespace helpers {
53+
template<auto>
54+
struct dummy_receiver {
55+
template<typename T>
56+
requires (!std::same_as<T, void>)
57+
void set_value_inline(T) {
58+
assert(std::is_constant_evaluated());
59+
}
60+
void set_value_inline() {
61+
assert(std::is_constant_evaluated());
62+
}
63+
64+
template<typename T>
65+
requires (!std::same_as<T, void>)
66+
void set_value_noinline(T) {
67+
assert(std::is_constant_evaluated());
68+
}
69+
void set_value_noinline() {
70+
assert(std::is_constant_evaluated());
71+
}
72+
};
73+
static_assert(Receives<dummy_receiver<[]{}>, void>);
74+
static_assert(Receives<dummy_receiver<[]{}>, int>);
75+
} /* namespace helpers */
76+
77+
template<typename T>
78+
concept Operation = !std::movable<T> && requires(T &t) {
79+
{ execution::start_inline(t) } -> std::same_as<bool>;
80+
};
81+
82+
/* We require move constructible, rather than movable, since lambdas can be
83+
* move constructible but not movable
84+
*/
85+
template<typename T>
86+
concept Sender = std::move_constructible<T> && requires(T t) {
87+
typename T::value_type;
88+
{ execution::connect(std::move(t), helpers::dummy_receiver<[]{}>{}) }
89+
-> Operation;
90+
};
4091

4192
template<typename E>
4293
requires requires(E &&e) { operator co_await(std::forward<E>(e)); }
@@ -54,6 +105,9 @@ auto make_awaiter(E &&e) {
54105
// sender_awaiter template.
55106
// ----------------------------------------------------------------------------
56107

108+
/* we can't declare S a sender here, since, if we do, it'd be impossible to
109+
* declare a member co_await that returns a sender_awaiter
110+
*/
57111
template<typename S, typename T = void>
58112
struct [[nodiscard]] sender_awaiter {
59113
private:
@@ -321,7 +375,7 @@ void run_forever(IoService ios) {
321375
}
322376
}
323377

324-
template<typename Sender>
378+
template<Sender Sender>
325379
requires std::same_as<typename Sender::value_type, void>
326380
void run(Sender s) {
327381
struct receiver {
@@ -337,7 +391,7 @@ void run(Sender s) {
337391
platform::panic("libasync: Operation hasn't completed and we don't know how to wait");
338392
}
339393

340-
template<typename Sender>
394+
template<Sender Sender>
341395
requires (!std::same_as<typename Sender::value_type, void>)
342396
typename Sender::value_type run(Sender s) {
343397
struct state {
@@ -369,7 +423,7 @@ typename Sender::value_type run(Sender s) {
369423
platform::panic("libasync: Operation hasn't completed and we don't know how to wait");
370424
}
371425

372-
template<typename Sender, Waitable IoService>
426+
template<Sender Sender, Waitable IoService>
373427
requires std::same_as<typename Sender::value_type, void>
374428
void run(Sender s, IoService ios) {
375429
struct state {
@@ -403,7 +457,7 @@ void run(Sender s, IoService ios) {
403457
}
404458
}
405459

406-
template<typename Sender, typename IoService>
460+
template<Sender Sender, typename IoService>
407461
requires (!std::same_as<typename Sender::value_type, void>)
408462
typename Sender::value_type run(Sender s, IoService ios) {
409463
struct state {
@@ -529,12 +583,12 @@ void detach_with_allocator(Allocator allocator, S sender) {
529583
detach_with_allocator<Allocator, S>(std::move(allocator), std::move(sender), [] { });
530584
}
531585

532-
template<typename S>
586+
template<Sender S>
533587
void detach(S sender) {
534588
return detach_with_allocator(frg::stl_allocator{}, std::move(sender));
535589
}
536590

537-
template<typename S, typename Cont>
591+
template<Sender S, typename Cont>
538592
void detach(S sender, Cont continuation) {
539593
return detach_with_allocator(frg::stl_allocator{}, std::move(sender), std::move(continuation));
540594
}

0 commit comments

Comments
 (0)