Streams (Partial I/O)

This section explains the ReadStream and WriteStream concepts for partial I/O operations.

Prerequisites

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;
    };

read_some Semantics

template<MutableBufferSequence Buffers>
IoAwaitable auto read_some(Buffers buffers);

Attempts to read up to buffer_size(buffers) bytes from the stream into the buffer sequence. Await-returns (error_code, std::size_t):

If buffer_size(buffers) > 0:

  • If !ec, then n >= 1 && n <= buffer_size(buffers). n bytes were read into the buffer sequence.

  • If ec, then n >= 0 && n <= buffer_size(buffers). n is the number of bytes read before the I/O condition arose.

If buffer_empty(buffers) is true, n is 0. The empty buffer is not itself a cause for error, but ec may reflect the state of the stream.

I/O conditions from the underlying system are reported via ec. Failures in the library itself (such as allocation failure) are reported via exceptions.

Throws: std::bad_alloc if coroutine frame allocation fails.

Partial Transfer

read_some may return fewer bytes than the buffer can hold:

char buf[1024];
auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));
// n might be 1, might be 500, might be 1024
// if !ec, then n >= 1

This matches underlying OS behavior—reads return when some data is available.

Example

template<ReadStream Stream>
task<> dump_stream(Stream& stream)
{
    char buf[256];

    for (;;)
    {
        auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));

        std::cout.write(buf, n);

        if (ec)
            break;
    }
}

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;
    };

write_some Semantics

template<ConstBufferSequence Buffers>
IoAwaitable auto write_some(Buffers buffers);

Attempts to write up to buffer_size(buffers) bytes from the buffer sequence to the stream. Await-returns (error_code, std::size_t):

If buffer_size(buffers) > 0:

  • If !ec, then n >= 1 && n <= buffer_size(buffers). n bytes were written from the buffer sequence.

  • If ec, then n >= 0 && n <= buffer_size(buffers). n is the number of bytes written before the I/O condition arose.

If buffer_empty(buffers) is true, n is 0. The empty buffer is not itself a cause for error, but ec may reflect the state of the stream.

I/O conditions from the underlying system are reported via ec. Failures in the library itself (such as allocation failure) are reported via exceptions.

Throws: std::bad_alloc if coroutine frame allocation fails.

Partial Transfer

write_some may write fewer bytes than provided:

auto [ec, n] = co_await stream.write_some(make_buffer(large_data));
// n might be less than large_data.size()

To write all data, loop until complete (or use the write() composed operation).

Type-Erasing Wrappers

any_read_stream

Wraps any ReadStream in a type-erased container:

#include <boost/capy/io/any_read_stream.hpp>

template<ReadStream S>
any_read_stream(S& stream);

The wrapped stream is referenced—the original 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);

any_stream

Wraps bidirectional streams (both ReadStream and WriteStream):

#include <boost/capy/io/any_stream.hpp>

template<ReadStream S>
    requires WriteStream<S>
any_stream(S& stream);

Wrapper Characteristics

All wrappers share these properties:

  • Reference semantics — Wrap existing objects without ownership

  • Preallocated coroutine frame — Zero steady-state allocation

  • Move-only — Non-copyable; moving transfers the cached frame

  • Lifetime requirement — Wrapped object must outlive wrapper

Example usage:

void process_stream(any_stream& stream);

tcp::socket socket;
// ... connect socket ...

any_stream wrapped{socket};  // Type erasure here
process_stream(wrapped);      // process_stream doesn't know about tcp::socket

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(mutable_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

<boost/capy/concept/read_stream.hpp>

ReadStream concept definition

<boost/capy/concept/write_stream.hpp>

WriteStream concept definition

<boost/capy/io/any_read_stream.hpp>

Type-erased read stream wrapper

<boost/capy/io/any_write_stream.hpp>

Type-erased write stream wrapper

<boost/capy/io/any_stream.hpp>

Type-erased bidirectional stream wrapper

You have now learned the stream concepts for partial I/O. Continue to Sources and Sinks to learn about complete I/O with EOF signaling.