Composed Operations

Corosio provides composed operations that build on the primitive read_some() and write_some() functions to provide higher-level guarantees.

Code snippets assume:

#include <boost/capy/read.hpp>
#include <boost/capy/write.hpp>
#include <boost/capy/buffers/string_dynamic_buffer.hpp>
#include <boost/capy/buffers.hpp>

namespace corosio = boost::corosio;
namespace capy = boost::capy;

The Problem with Primitives

The primitive operations read_some() and write_some() provide no guarantees about how much data is transferred:

char buf[1024];
auto [ec, n] = co_await s.read_some(
    capy::mutable_buffer(buf, sizeof(buf)));
// n could be 1, 100, 500, or 1024 - no guarantee

For many use cases, you need to transfer a specific amount of data. Composed operations provide these guarantees.

capy::read()

The read() function reads until the buffer is full or an error occurs:

char buf[1024];
auto [ec, n] = co_await capy::read(
    stream, capy::mutable_buffer(buf, sizeof(buf)));

// Either:
// - n == 1024 and ec is default (success)
// - ec == capy::cond::eof and n < 1024 (reached end of stream)
// - ec is some other error

Signature

auto
read(
    ReadStream auto& stream,
    MutableBufferSequence auto const& buffers) ->
        capy::io_task<std::size_t>;

Behavior

  1. Calls read_some() repeatedly until all buffers are filled

  2. If read_some() returns an error (including cond::eof), returns immediately with bytes read so far

  3. On success, returns total bytes (equals buffer_size(buffers))

capy::read() into a dynamic buffer

A second overload reads until EOF, growing the buffer as needed. Use capy::string_dynamic_buffer to wrap a std::string:

std::string content;
auto [ec, n] = co_await capy::read(
    stream, capy::string_dynamic_buffer(&content));

// On success (EOF reached): ec is default, n is total bytes read
// On error: ec is the error, n is bytes read before the error

Signature

auto
read(
    ReadStream auto& stream,
    DynamicBufferParam auto&& buffers,
    std::size_t initial_amount = 2048) ->
        capy::io_task<std::size_t>;

Behavior

  1. Prepares initial_amount bytes via buffers.prepare(), then reads

  2. Grows 1.5x when the prepared buffer fills completely

  3. On cond::eof: returns success with total bytes read

  4. On any other error: returns immediately with bytes read so far

capy::write()

The write() function writes all data or fails:

std::string msg = "Hello, World!";
auto [ec, n] = co_await capy::write(
    stream, capy::const_buffer(msg.data(), msg.size()));

// Either:
// - n == msg.size() and ec is default (all data written)
// - ec is an error

Signature

auto
write(
    WriteStream auto& stream,
    ConstBufferSequence auto const& buffers) ->
        capy::io_task<std::size_t>;

Behavior

  1. Calls write_some() repeatedly until all buffers are written

  2. If an error occurs, returns immediately with bytes written so far

  3. On success, returns total bytes (equals buffer_size(buffers))

buffer_slice Helper

Both read() and write() use capy::buffer_slice internally to track progress through a buffer sequence. buffer_slice returns a view over a byte range of the underlying sequence. It exposes the bytes not yet transferred via data() and advances past completed bytes with remove_prefix(n):

#include <boost/capy/buffers/buffer_slice.hpp>

std::array<capy::mutable_buffer, 2> bufs = {
    capy::mutable_buffer(header, 16),
    capy::mutable_buffer(body, 1024)
};

auto slice = capy::buffer_slice(bufs);

// After reading 20 bytes:
slice.remove_prefix(20);
// slice.data() now represents: 4 bytes of header remaining + full body

Interface

template<class BufferSequence>
    requires MutableBufferSequence<BufferSequence>
          || ConstBufferSequence<BufferSequence>
auto
buffer_slice(
    BufferSequence const& seq,
    std::size_t offset = 0,
    std::size_t length = std::numeric_limits<std::size_t>::max());

The returned object satisfies the Slice concept (and MutableSlice when seq is a mutable buffer sequence). Its data() member returns a buffer sequence covering the remaining bytes, and remove_prefix(n) advances the slice past n consumed bytes.

seq must outlive the slice and any buffer sequence obtained from its data(). Passing a temporary buffer sequence is rejected at compile time; hoist it into a named variable first.

Error Handling Patterns

Structured Bindings with EOF Check

auto [ec, n] = co_await capy::read(stream, buf);
if (ec)
{
    if (ec == capy::cond::eof)
        std::cout << "End of stream, read " << n << " bytes\n";
    else
        std::cerr << "Error: " << ec.message() << "\n";
}

Exception Pattern

// For write (EOF doesn't apply)
auto [wec, n] = co_await capy::write(stream, buf);
if (wec)
    capy::detail::throw_system_error(wec);

// For read (need to handle EOF)
auto [rec, rn] = co_await capy::read(stream, buf);
if (rec && rec != capy::cond::eof)
    capy::detail::throw_system_error(rec);

Cancellation

Composed operations support cancellation through the affine protocol. When cancelled, they return with cond::canceled and the partial byte count.

auto [ec, n] = co_await capy::read(stream, large_buffer);
if (ec == capy::cond::canceled)
    std::cout << "Cancelled after reading " << n << " bytes\n";

Performance Considerations

Single vs. Multiple Buffers

For optimal performance with multiple buffers:

// Efficient: single system call per read_some()
std::array<capy::mutable_buffer, 2> bufs = {...};
co_await capy::read(stream, bufs);

// Less efficient: may require more system calls
co_await capy::read(stream, buf1);
co_await capy::read(stream, buf2);

Buffer Sizing

Choose buffer sizes that match your expected data:

  • Too small: More system calls

  • Too large: Memory waste

For unknown-length data (like HTTP responses), use the dynamic buffer overload:

std::string response;
co_await capy::read(stream, capy::string_dynamic_buffer(&response));

Example: HTTP Response Reading

capy::task<std::string> read_http_response(corosio::io_stream& stream)
{
    std::string response;
    auto [ec, n] = co_await capy::read(
        stream, capy::string_dynamic_buffer(&response));

    if (ec)
        capy::detail::throw_system_error(ec);

    co_return response;
}

Next Steps