Skip to content

*: rewrite to concepts #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
build
book
4 changes: 4 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
- [run and run_forever](headers/basic/run.md)
- [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>);
```
10 changes: 6 additions & 4 deletions docs/src/headers/basic/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ via the IO service.
## Prototype

```cpp
template<typename IoService>
template<Waitable IoService>
void run_forever(IoService ios); // (1)

template<typename Sender>
Sender::value_type run(Sender s); // (2)

template<typename Sender, typename IoService>
template<typename Sender, Waitable IoService>
Sender::value_type run(Sender s, IoService ios); // (3)
```

Expand All @@ -24,11 +24,13 @@ inline as there's no way to wait for it to complete.

### Requirements

`IoService` is an [IO service](io-service.md), and `Sender` is a sender.
`IoService` is an [IO service](/io-service.md), and must be a
[Waitable](./waitable.md), and `Sender` is a sender.

### Arguments

- `IoService` - the IO service to use to wait for completion.
- `IoService` - the IO service to use to wait for completion. Must fulfill the
[Waitable](./waitable.md) concept.
- `Sender` - the sender to start.

### Return value
Expand Down
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>);
```
33 changes: 33 additions & 0 deletions docs/src/headers/basic/waitable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# `concept Waitable`

The `Waitable` concept holds all the requirements for an [IO
service](/io-service.md). Presently, this is only a `wait()` method.

## Prototype

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

### Requirements

`T` provides a instance wait method that can be called on a value.

## Examples

```cpp
struct io_service {
/** \pre loop must still be alive */
void wait() {
auto loop = m_loop.lock();
assert(loop);
loop->wait();
}
private:
friend struct event;
io_service(std::weak_ptr<event> e) : m_loop { std::move(e) } {}
std::weak_ptr<event> m_loop;
};
static_assert(async::Waitable<io_service>);
```
1 change: 1 addition & 0 deletions docs/src/headers/wait-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# async/wait-group.hpp
2 changes: 2 additions & 0 deletions docs/src/io-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ The IO service must provide one method: `void wait()`. This method is called whe
there is no more work to do currently. It waits for any event to happen, and wakes
up the appropriate coroutine/operation which awaited the event.

See also: the [Waitable](/headers/basic/waitable.md) concept.

**Note:** `async::run` and `async::run_forever` (see [here](headers/basic/run.md#prototype))
take the IO service by value, not by reference.

Expand Down
43 changes: 27 additions & 16 deletions include/async/algorithm.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <algorithm>
#include <concepts>

#include <async/basic.hpp>
#include <async/cancellation.hpp>
Expand All @@ -9,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 @@ -21,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 @@ -58,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 @@ -141,7 +142,7 @@ template<typename Sender, typename F>
struct [[nodiscard]] transform_sender;

template<typename Sender, typename F>
requires (!std::is_same_v<typename Sender::value_type, void>)
requires (!std::same_as<typename Sender::value_type, void>)
struct [[nodiscard]] transform_sender<Sender, F> {
using value_type = std::invoke_result_t<F, typename Sender::value_type>;

Expand All @@ -160,11 +161,11 @@ struct [[nodiscard]] transform_sender<Sender, F> {
};

template<typename Sender, typename F>
requires std::is_same_v<typename Sender::value_type, void>
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 @@ -233,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 @@ -248,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 @@ -325,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 @@ -335,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 @@ -350,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 @@ -449,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 @@ -494,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 @@ -510,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 @@ -702,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 @@ -777,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 @@ -786,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
Loading