Buffer Algorithms

Measuring

buffer_size returns the total number of bytes across every buffer in a sequence:

auto buf1 = capy::make_buffer("hello"sv);  // 5 bytes
auto buf2 = capy::make_buffer("world"sv);  // 5 bytes
auto combined = std::array{buf1, buf2};

std::size_t total = capy::buffer_size(combined);  // 10

buffer_length returns the number of buffers in the sequence:

auto single = capy::make_buffer("hello"sv);
capy::buffer_length(single);  // 1

auto arr = std::array{buf1, buf2, buf3};
capy::buffer_length(arr);  // 3

These two names are easy to confuse, so read them as answering different questions:

  • buffer_size — how many bytes of data? This is what I/O operations care about.

  • buffer_length — how many buffers hold it? This is the element count of the sequence, the analogue of std::ranges::size.

A three-buffer sequence of 100 bytes each has a buffer_size of 300 and a buffer_length of 3. You want buffer_size far more often.

buffer_empty reports whether a sequence carries no data, either because it holds no buffers or because every buffer has size zero:

capy::const_buffer empty_buf;
capy::buffer_empty(empty_buf);  // true

capy::const_buffer non_empty("data", 4);
capy::buffer_empty(non_empty);  // false

Copying

buffer_copy copies data from one buffer sequence to another and returns the number of bytes copied:

char source_data[] = "hello world";
char dest_data[20];

capy::const_buffer src(source_data, 11);
capy::mutable_buffer dst(dest_data, 20);

std::size_t copied = capy::buffer_copy(dst, src);  // 11

Its at_most parameter caps the transfer, which is useful for protocols with size limits:

// Copy at most 5 bytes
std::size_t copied = capy::buffer_copy(dst, src, 5);

Source and target need not have the same shape:

// Source: 3 buffers
std::array<capy::const_buffer, 3> src = {buf1, buf2, buf3};

// Target: 2 buffers with different sizes
std::array<capy::mutable_buffer, 2> dst = {large_buf, small_buf};

// Copies across buffer boundaries as needed
std::size_t copied = capy::buffer_copy(dst, src);

The algorithm fills target buffers in order, reading from source buffers as needed. It handles a source buffer spanning several targets, and the reverse.

Partial Transfer Loops

Real transfers move some of the bytes, not all of them. Drive the loop with a consuming_buffers cursor, described on consuming_buffers.

template<capy::ReadStream Stream, capy::MutableBufferSequence Buffers>
capy::task<std::size_t> read_full(Stream& stream, Buffers buffers)
{
    capy::consuming_buffers remaining(buffers);
    std::size_t const total_size = capy::buffer_size(buffers);
    std::size_t total = 0;

    while (total < total_size)
    {
        auto [ec, n] = co_await stream.read_some(remaining.data());
        remaining.consume(n);
        total += n;
        if (ec)
            co_return total;
    }

    co_return total;
}
template<capy::WriteStream Stream, capy::ConstBufferSequence Buffers>
capy::task<std::size_t> write_full(Stream& stream, Buffers buffers)
{
    capy::consuming_buffers remaining(buffers);
    std::size_t const total_size = capy::buffer_size(buffers);
    std::size_t total = 0;

    while (total < total_size)
    {
        auto [ec, n] = co_await stream.write_some(remaining.data());
        remaining.consume(n);
        total += n;
        if (ec)
            co_return total;
    }

    co_return total;
}

Custom Buffer Types

Any memory region can be a buffer, including a memory-mapped one:

// Memory-mapped file
void* mapped = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
capy::const_buffer file_buf(mapped, file_size);
co_await capy::write(socket, file_buf);  // Zero-copy network transmission

You can also define your own type satisfying the concepts:

class chunked_buffer_sequence
{
    std::vector<std::vector<char>> chunks_;

public:
    auto begin() const { return chunk_iterator(chunks_.begin()); }
    auto end() const { return chunk_iterator(chunks_.end()); }
};
// Satisfies ConstBufferSequence—works with all algorithms

Here chunk_iterator is a small bidirectional iterator whose operator* returns each chunk as a const_buffer.