Streams (Partial I/O)
This section explains the ReadStream and WriteStream concepts for partial I/O operations.
ReadStream
A type satisfies ReadStream if it provides partial read operations via read_some:
template<typename T>
concept ReadStream =
requires(T& stream, mutable_buffer_archetype buffers)
{
{ stream.read_some(buffers) } -> IoAwaitable;
requires awaitable_decomposes_to<
decltype(stream.read_some(buffers)),
std::error_code, std::size_t>;
};
The requires clause names a single representative buffer (mutable_buffer_archetype) because a C++ concept cannot say "works with every buffer sequence." The real contract is that read_some accepts any MutableBufferSequence—one buffer or a range; the archetype only samples that requirement.
read_some Semantics
See ReadStream for the full contract: return-value semantics, error reporting, throws, and buffer lifetime.
WriteStream
A type satisfies WriteStream if it provides partial write operations via write_some:
template<typename T>
concept WriteStream =
requires(T& stream, const_buffer_archetype buffers)
{
{ stream.write_some(buffers) } -> IoAwaitable;
requires awaitable_decomposes_to<
decltype(stream.write_some(buffers)),
std::error_code, std::size_t>;
};
As with ReadStream, the const_buffer_archetype is only a representative: the real contract is that write_some accepts any ConstBufferSequence, which a C++ concept cannot fully express.
write_some Semantics
See WriteStream for the full contract: return-value semantics, error reporting, throws, and buffer lifetime.
Type-Erasing Wrappers
any_read_stream
Wraps any ReadStream in a type-erased container:
#include <boost/capy/io/any_read_stream.hpp>
// Owning: takes ownership of a moved-in stream
template<ReadStream S>
any_read_stream(S stream);
// Reference: wraps by pointer without ownership
template<ReadStream S>
any_read_stream(S* stream);
Each wrapper has two construction modes. Passing an object by value takes ownership: the wrapper moves the stream into internally allocated storage. Passing a pointer wraps the pointed-to stream by reference, and that stream must outlive the wrapper.
any_write_stream
Wraps any WriteStream:
#include <boost/capy/io/any_write_stream.hpp>
template<WriteStream S>
any_write_stream(S stream); // owning
template<WriteStream S>
any_write_stream(S* stream); // reference
any_stream
Wraps bidirectional streams (both ReadStream and WriteStream):
#include <boost/capy/io/any_stream.hpp>
template<class S>
requires ReadStream<S> && WriteStream<S>
any_stream(S stream); // owning
template<class S>
requires ReadStream<S> && WriteStream<S>
any_stream(S* stream); // reference
Wrapper Characteristics
All wrappers share these properties:
-
Owning or reference: By-value construction owns a moved-in object; pointer construction wraps by reference
-
Preallocated coroutine frame: Zero steady-state allocation
-
Move-only: Non-copyable; moving transfers the cached frame
-
Lifetime requirement: A pointer-wrapped object must outlive the wrapper
Example usage:
void process_stream(any_stream& stream);
auto [client, server] = test::make_stream_pair();
any_stream wrapped{&client}; // Type erasure, references the existing stream
process_stream(wrapped); // process_stream doesn't know about test::stream
Example: Echo Server with any_stream
// echo.hpp - Header only declares the signature
task<> handle_connection(any_stream& stream);
// echo.cpp - Implementation in separate translation unit
task<> handle_connection(any_stream& stream)
{
char buf[1024];
for (;;)
{
auto [ec, n] = co_await stream.read_some(make_buffer(buf));
auto [wec, wn] = co_await write(stream, const_buffer(buf, n));
if (ec)
break;
if (wec)
break;
}
}
The implementation doesn’t know the concrete stream type. It compiles once and works with any transport.
Reference
| Header | Description |
|---|---|
|
ReadStream concept definition |
|
WriteStream concept definition |
|
Type-erased read stream wrapper |
|
Type-erased write stream wrapper |
|
Type-erased bidirectional stream wrapper |