Conversation
| namespace coro::ranges | ||
| { | ||
| template<concepts::async_streamable previous_stream_t, typename value_t> | ||
| class await_view_base |
There was a problem hiding this comment.
Used a base class because coro::task needs a different treatment
| template<concepts::async_streamable Rng> | ||
| auto operator()(Rng rng) const -> coro::task<void> | ||
| { | ||
| while (co_await rng.next()) {} |
There was a problem hiding this comment.
Drain basically just extracts values until they run out
|
|
||
| // Should be safe, because socket_stream gets moved | ||
| // into further pipe objects | ||
| auto next() -> coro::task<std::optional<std::span<const std::byte>>> |
There was a problem hiding this comment.
Maybe change to std::optional<std;:expected<std::span<const std::byte>, io_status>>? Looks too verbose, so maybe it's better to use std::expected everywhere
| else | ||
| { | ||
| return std::forward<Adaptor>(partial)(std::forward<Rng>(rng)); | ||
| } |
There was a problem hiding this comment.
Not sure if it's the best way to handle references, but I don't know the better
| } | ||
|
|
||
| template<typename Adaptor, typename... Args> | ||
| struct _partial : public concepts::_async_adaptor |
There was a problem hiding this comment.
Took this pattern from stdlib code, it saves args and then just appends sync stream as the first arg
| constexpr auto unwrap_return_value(std::reference_wrapper<T> value) noexcept -> T& | ||
| { | ||
| return value.get(); | ||
| } |
There was a problem hiding this comment.
Because std::optional doesn't support references, this is a way around it. But maybe forbidding references will be better
|
I recently learned about Receiver/Sender in C++ 26, it's basically the same thing I'm doing here. I think I should make the API compatible with it |
While working with std::ranges, I've got this idea. We already have nice coroutine primitives and networking API in libcoro, but when it comes to stream-style processing, everything still ends up as manual while (co_await ...) loops. It works, but it’s not composable, and it doesn’t scale nicely once you add filtering, logging, etc.
So I experimented with a small async range layer built on top of existing coroutine primitives.
Idea
An async stream is just a type that provides:
How it looks:
Everything remains fully lazy and very nice to use with coroutines.
Everything gets completely inlined with no overhead (checked on clang).
My PR is a very early draft, because I just wanted to show this idea.
Idk if this idea makes sense, so I need feedback on the overall direction before taking this further, so I didn't focus on buffering, error handling and cancellation semantics yet
How could it look (when finished)