Skip to content

Commit

Permalink
basic: add Sender, Receives and Operation concepts
Browse files Browse the repository at this point in the history
  • Loading branch information
ArsenArsen committed Sep 17, 2022
1 parent d5f9f08 commit 06dacf5
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 20 deletions.
3 changes: 3 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
- [detached](headers/basic/detached.md)
- [spawn](headers/basic/spawn.md)
- [Waitable](headers/basic/waitable.md)
- [Sender](headers/basic/sender.md)
- [Operation](headers/basic/operation.md)
- [Receives<T>](headers/basic/receives.md)
- [async/result.hpp](headers/result.md)
- [async/oneshot.hpp](headers/oneshot-event.md)
- [async/wait-group.hpp](headers/wait-group.md)
Expand Down
41 changes: 41 additions & 0 deletions docs/src/headers/basic/operation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# `concept Operation`

The `Operation` concept holds all the requirements for an
[operation](/sender-receiver.md).

## Prototype

```cpp
template<typename T>
concept Operation = ...;
```

### Requirements

`T` can be started via the [start\_inline](/headers/execution.md) CPO.

## Examples

```cpp
template <typename Receiver>
struct write_operation {
write_operation(write_sender s, Receiver r)
: req_{}, handle_{s.handle}, bufs_{s.bufs}, nbufs_{s.nbufs}, r_{std::move(r)} { }

write_operation(const write_operation &) = delete;
write_operation &operator=(const write_operation &) = delete;
write_operation(write_operation &&) = delete;
write_operation &operator=(write_operation &&) = delete;

bool start_inline() { /* omitted for brevity */ }

private:
uv_write_t req_;
uv_stream_t *handle_;
const uv_buf_t *bufs_;
size_t nbufs_;

Receiver r_;
};
static_assert(async::Operation<write_operation<noop_receiver>>);
```
42 changes: 42 additions & 0 deletions docs/src/headers/basic/receives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# `concept Receives`

The `Receives<T>` concept holds all the requirements for a
[receiver](/sender-receiver.md) that can receive a `T` value (or none, when `T`
is `void`).

## Prototype

```cpp
template<typename T, typename Receives>
concept Receives = ...;
```

### Requirements

A `set_value_inline` and `set_value_noinline` members, which can be called with
a `T&&` value, or no parameters, if `T` is `void`.

## Examples

```cpp
struct discard_receiver {
template<typename T>
void set_value_inline(T) {
assert(std::is_constant_evaluated());
}
void set_value_inline() {
assert(std::is_constant_evaluated());
}

template<typename T>
void set_value_noinline(T) {
assert(std::is_constant_evaluated());
}
void set_value_noinline() {
assert(std::is_constant_evaluated());
}
};
static_assert(async::Receives<discard_receiver, void>);
static_assert(async::Receives<discard_receiver, int>);
static_assert(async::Receives<discard_receiver, std::string>);
```
35 changes: 35 additions & 0 deletions docs/src/headers/basic/sender.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `concept Sender`

The `Sender` concept holds all the requirements for a
[sender](/sender-receiver.md).

## Prototype

```cpp
template<typename T>
concept Sender = ...;
```

### Requirements

`T` has a `value_type`, is move constructible, and can be
[connected](/headers/execution.md).

## Examples

```cpp
struct [[nodiscard]] write_sender {
using value_type = int; // Status code

uv_stream_t *handle;
const uv_buf_t *bufs;
size_t nbufs;
};

/* operation omitted for brevity */
template <typename Receiver>
/*operation*/<Receiver> connect(write_sender s, Receiver r) {
return {s, std::move(r)};
}
static_assert(async::Sender<write_sender>);
```
38 changes: 24 additions & 14 deletions include/async/algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace async {

template<typename Sender, typename Receiver>
template<Sender Sender, Receives<typename Sender::value_type> Receiver>
struct connect_helper {
using operation = execution::operation_t<Sender, Receiver>;

Expand All @@ -22,7 +22,7 @@ struct connect_helper {
Receiver r;
};

template<typename Sender, typename Receiver>
template<Sender Sender, Receives<typename Sender::value_type> Receiver>
connect_helper<Sender, Receiver> make_connect_helper(Sender s, Receiver r) {
return {std::move(s), std::move(r)};
}
Expand Down Expand Up @@ -59,7 +59,7 @@ template<typename F>
struct [[nodiscard]] invocable_sender {
using value_type = std::invoke_result_t<F>;

template<typename R>
template<Receives<value_type> R>
invocable_operation<F, R> connect(R r) {
return {std::move(f), std::move(r)};
}
Expand Down Expand Up @@ -165,7 +165,7 @@ requires std::same_as<typename Sender::value_type, void>
struct [[nodiscard]] transform_sender<Sender, F> {
using value_type = std::invoke_result_t<F>;

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

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

template<typename C, typename ST, typename SE>
template<std::invocable<> C, Sender ST, Sender SE>
requires std::same_as<typename ST::value_type, typename SE::value_type>
ite_sender<C, ST, SE> ite(C cond, ST then_s, SE else_s) {
return {std::move(cond), std::move(then_s), std::move(else_s)};
}
Expand Down Expand Up @@ -326,7 +327,7 @@ struct repeat_while_sender {
return {std::move(*this)};
}

template<typename R>
template<Receives<value_type> R>
repeat_while_operation<C, SF, R> connect(R receiver) {
return {std::move(cond), std::move(factory), std::move(receiver)};
}
Expand All @@ -336,6 +337,10 @@ struct repeat_while_sender {
};

template<typename C, typename SF>
requires std::move_constructible<C> && requires (C c, SF sf) {
{ c() } -> std::convertible_to<bool>;
{ sf() } -> Sender;
}
repeat_while_sender<C, SF> repeat_while(C cond, SF factory) {
return {std::move(cond), std::move(factory)};
}
Expand All @@ -351,7 +356,7 @@ template<typename... Functors>
struct race_and_cancel_sender {
using value_type = void;

template<typename Receiver>
template<Receives<value_type> Receiver>
friend race_and_cancel_operation<Receiver, frg::tuple<Functors...>,
std::index_sequence_for<Functors...>>
connect(race_and_cancel_sender s, Receiver r) {
Expand Down Expand Up @@ -450,7 +455,8 @@ operator co_await(race_and_cancel_sender<Functors...> s) {
return {std::move(s)};
}

template<typename... Functors>
template<std::invocable<cancellation_token>... Functors>
requires ((Sender<std::invoke_result_t<Functors, cancellation_token>>) && ...)
race_and_cancel_sender<Functors...> race_and_cancel(Functors... fs) {
return {{fs...}};
}
Expand Down Expand Up @@ -495,7 +501,7 @@ struct [[nodiscard]] let_sender {
using imm_type = std::invoke_result_t<Pred>;
using value_type = typename std::invoke_result_t<Func, std::add_lvalue_reference_t<imm_type>>::value_type;

template<typename Receiver>
template<Receives<value_type> Receiver>
friend let_operation<Receiver, Pred, Func>
connect(let_sender s, Receiver r) {
return {std::move(s.pred), std::move(s.func), std::move(r)};
Expand All @@ -511,7 +517,10 @@ operator co_await(let_sender<Pred, Func> s) {
return {std::move(s)};
}

template <typename Pred, typename Func>
template <std::invocable<> Pred, typename Func>
requires requires (Func func, Pred pred) {
func(std::declval<std::add_lvalue_reference_t<decltype(pred())>>());
}
let_sender<Pred, Func> let(Pred pred, Func func) {
return {std::move(pred), std::move(func)};
}
Expand Down Expand Up @@ -703,7 +712,7 @@ sequence_sender<Senders...> sequence(Senders ...senders) {
return {frg::tuple<Senders...>{std::move(senders)...}};
}

template <typename ...Senders>
template <Sender ...Senders>
sender_awaiter<sequence_sender<Senders...>, typename sequence_sender<Senders...>::value_type>
operator co_await(sequence_sender<Senders...> s) {
return {std::move(s)};
Expand Down Expand Up @@ -778,7 +787,7 @@ template <typename ...Senders> requires (sizeof...(Senders) > 0)
struct [[nodiscard]] when_all_sender {
using value_type = void;

template<typename Receiver>
template<Receives<value_type> Receiver>
friend when_all_operation<Receiver, Senders...>
connect(when_all_sender s, Receiver r) {
return {std::move(s.senders), std::move(r)};
Expand All @@ -787,7 +796,8 @@ struct [[nodiscard]] when_all_sender {
frg::tuple<Senders...> senders;
};

template <typename ...Senders> requires (sizeof...(Senders) > 0)
template <Sender ...Senders>
requires (sizeof...(Senders) > 0)
when_all_sender<Senders...> when_all(Senders ...senders) {
return {frg::tuple<Senders...>{std::move(senders)...}};
}
Expand Down
66 changes: 60 additions & 6 deletions include/async/basic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,57 @@ namespace corons = std::experimental;
#endif

namespace async {
template<typename T, typename Value>
concept Receives = std::movable<T>
&& (std::same_as<Value, void> ?
requires(T t) {
{ t.set_value_inline() } -> std::same_as<void>;
{ t.set_value_noinline() } -> std::same_as<void>;
}
: requires(T t) {
{ t.set_value_inline(std::declval<Value>()) } -> std::same_as<void>;
{ t.set_value_noinline(std::declval<Value>()) } -> std::same_as<void>;
});

namespace helpers {
template<auto>
struct dummy_receiver {
template<typename T>
requires (!std::same_as<T, void>)
void set_value_inline(T) {
assert(std::is_constant_evaluated());
}
void set_value_inline() {
assert(std::is_constant_evaluated());
}

template<typename T>
requires (!std::same_as<T, void>)
void set_value_noinline(T) {
assert(std::is_constant_evaluated());
}
void set_value_noinline() {
assert(std::is_constant_evaluated());
}
};
static_assert(Receives<dummy_receiver<[]{}>, void>);
static_assert(Receives<dummy_receiver<[]{}>, int>);
} /* namespace helpers */

template<typename T>
concept Operation = !std::movable<T> && requires(T &t) {
{ execution::start_inline(t) } -> std::same_as<bool>;
};

/* We require move constructible, rather than movable, since lambdas can be
* move constructible but not movable
*/
template<typename T>
concept Sender = std::move_constructible<T> && requires(T t) {
typename T::value_type;
{ execution::connect(std::move(t), helpers::dummy_receiver<[]{}>{}) }
-> Operation;
};

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

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

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

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

template<typename Sender, Waitable IoService>
template<Sender Sender, Waitable IoService>
requires std::same_as<typename Sender::value_type, void>
void run(Sender s, IoService ios) {
struct state {
Expand Down Expand Up @@ -403,7 +457,7 @@ void run(Sender s, IoService ios) {
}
}

template<typename Sender, typename IoService>
template<Sender Sender, typename IoService>
requires (!std::same_as<typename Sender::value_type, void>)
typename Sender::value_type run(Sender s, IoService ios) {
struct state {
Expand Down Expand Up @@ -529,12 +583,12 @@ void detach_with_allocator(Allocator allocator, S sender) {
detach_with_allocator<Allocator, S>(std::move(allocator), std::move(sender), [] { });
}

template<typename S>
template<Sender S>
void detach(S sender) {
return detach_with_allocator(frg::stl_allocator{}, std::move(sender));
}

template<typename S, typename Cont>
template<Sender S, typename Cont>
void detach(S sender, Cont continuation) {
return detach_with_allocator(frg::stl_allocator{}, std::move(sender), std::move(continuation));
}
Expand Down

0 comments on commit 06dacf5

Please sign in to comment.