Buffer Sequences

Corosio I/O operations work with buffer sequences from Boost.Capy. This page explains how to use buffers effectively.

Code snippets assume:

#include <boost/capy/buffers.hpp>
namespace capy = boost::capy;

Buffer Types

mutable_buffer

A writable region of memory:

char data[1024];
capy::mutable_buffer buf(data, sizeof(data));

const_buffer

A read-only region of memory:

std::string msg = "Hello";
capy::const_buffer buf(msg.data(), msg.size());

Creating Buffers

From Raw Arrays

char data[1024];
capy::mutable_buffer mbuf(data, sizeof(data));

const char* str = "Hello";
capy::const_buffer cbuf(str, 5);

From std::string

std::string s = "Hello, World!";

// Writable (be careful with string invalidation)
capy::mutable_buffer mbuf(s.data(), s.size());

// Read-only
capy::const_buffer cbuf(s.data(), s.size());

From std::vector

std::vector<char> vec(1024);
capy::mutable_buffer buf(vec.data(), vec.size());

Buffer Sequences

I/O operations accept sequences of buffers for scatter/gather I/O:

Single Buffer

A single buffer is a valid buffer sequence:

capy::mutable_buffer buf(data, size);
co_await sock.read_some(buf);  // Works directly

Multiple Buffers

Use arrays or vectors of buffers:

// Array of buffers
std::array<capy::mutable_buffer, 2> bufs = {
    capy::mutable_buffer(header, header_size),
    capy::mutable_buffer(body, body_size)
};
co_await sock.read_some(bufs);

// Vector of buffers
std::vector<capy::const_buffer> send_bufs;
send_bufs.push_back(capy::const_buffer(header.data(), header.size()));
send_bufs.push_back(capy::const_buffer(body.data(), body.size()));
co_await sock.write_some(send_bufs);

Buffer Sequence Concepts

Corosio uses concepts from Capy:

// Readable buffers (for writing to sockets)
template<capy::ConstBufferSequence ConstBufferSequence>
auto write_some(ConstBufferSequence const& buffers);

// Writable buffers (for reading from sockets)
template<capy::MutableBufferSequence MutableBufferSequence>
auto read_some(MutableBufferSequence const& buffers);

A type satisfies these concepts if it’s iterable and yields buffer types.

Buffer Size

Get the total size of a buffer sequence:

std::array<capy::mutable_buffer, 2> bufs = {...};
std::size_t total = capy::buffer_size(bufs);

buffer_slice

capy::buffer_slice returns a view over a byte range of a buffer 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:
auto [ec, n] = co_await sock.read_some(slice.data());
slice.remove_prefix(n);  // Advance past the bytes read

// slice.data() now represents the remaining unread portion

This is used internally by read() and write() but can be used directly. The underlying sequence must outlive the slice and any sequence obtained from its data().

buffer_param

The buffer_param class type-erases buffer sequences:

#include <boost/corosio/detail/buffer_param.hpp>

void accept_any_buffer(corosio::buffer_param buffers)
{
    capy::mutable_buffer temp[8];
    std::size_t n = buffers.copy_to(temp, 8);
    // Use temp[0..n-1]
}

// Works with any buffer sequence (implicit conversion)
std::array<capy::mutable_buffer, 2> bufs = {...};
accept_any_buffer(bufs);

This enables non-templated code to work with any buffer type.

Memory Safety

Lifetime

Buffers don’t own memory. The underlying storage must outlive the I/O operation:

// WRONG: buffer outlives string
capy::task<void> bad_example(corosio::tcp_socket& sock)
{
    capy::const_buffer buf;
    {
        std::string temp = "Hello";
        buf = capy::const_buffer(temp.data(), temp.size());
    }  // temp destroyed here!

    co_await sock.write_some(buf);  // Undefined behavior
}

// CORRECT: keep storage alive
capy::task<void> good_example(corosio::tcp_socket& sock)
{
    std::string msg = "Hello";
    co_await sock.write_some(
        capy::const_buffer(msg.data(), msg.size()));
}

String Invalidation

Be careful when using std::string as buffer storage:

std::string s = "Hello";
capy::mutable_buffer buf(s.data(), s.size());

s += " World";  // May reallocate, invalidating buf!

// Use buf here: UNDEFINED BEHAVIOR

Either:

  • Reserve sufficient capacity upfront

  • Don’t modify the string while the buffer is in use

  • Create a new buffer after modification

Scatter/Gather I/O

Multiple buffers can be used for efficient scatter/gather operations:

Reading into Multiple Buffers (Scatter)

struct message_header { ... };
char body[1024];

std::array<capy::mutable_buffer, 2> read_bufs = {
    capy::mutable_buffer(&header, sizeof(header)),
    capy::mutable_buffer(body, sizeof(body))
};

auto [ec, n] = co_await sock.read_some(read_bufs);
// Data fills header first, then body

Writing from Multiple Buffers (Gather)

std::string header = "HTTP/1.1 200 OK\r\n\r\n";
std::string body = "Hello, World!";

std::array<capy::const_buffer, 2> write_bufs = {
    capy::const_buffer(header.data(), header.size()),
    capy::const_buffer(body.data(), body.size())
};

auto [ec, n] = co_await sock.write_some(write_bufs);
// Sends header followed by body in a single operation

Example: Reading a Fixed-Size Header

struct packet_header
{
    std::uint32_t magic;
    std::uint32_t length;
};

capy::task<packet_header> read_header(corosio::io_stream& stream)
{
    packet_header header;
    auto [ec, n] = co_await capy::read(
        stream, capy::mutable_buffer(&header, sizeof(header)));

    if (ec)
        throw std::system_error(ec);

    return header;
}

Next Steps