Buffer Types

This section introduces Capy’s fundamental buffer types: const_buffer and mutable_buffer.

Prerequisites

Buffers Are Handles

A const_buffer or mutable_buffer is a handle: a non-owning (pointer, size) view of memory it does not own. Constructing one copies no bytes, and destroying one frees nothing.

This splits lifetime responsibility cleanly:

  • You own the bytes. The memory a buffer refers to—a stack array, a std::string, a slab from your allocator—is yours to keep alive. It must remain valid for the entire duration of any operation you hand the buffer to, including across the suspension points of a co_await-ed I/O operation.

  • The library owns the handles. Capy creates and manages buffer handles and handle-sequences on your behalf—the buffers a dynamic buffer exposes through prepare/data, the sub-range a buffer_slice produces, the descriptors a type-erased stream passes to the OS. Each such handle is valid only for the window its API documents, typically until the next call that mutates the owner.

The library never copies or takes ownership of your bytes through a buffer; it only moves handles. This split explains every buffer-lifetime rule in this chapter.

Why void*, Not std::byte?

std::byte imposes a semantic opinion. It says "this is raw bytes"—but that is itself an opinion about the data’s nature.

POSIX uses void* for buffers. This expresses semantic neutrality: "I move memory without opining on what it contains." The OS doesn’t care if the bytes represent text, integers, or compressed data—it moves them.

Two concrete forces favor void* specifically over std::span<std::byte>:

  • Platform types already use it. The OS structures Capy maps onto—iovec’s `iov_base, WSABUF’s `buf—are void*/char*. Erasing to void* makes conversion to those structures a layout match rather than a reinterpretation.

  • Callers supply many element types. User data arrives as char[], unsigned char[], std::byte[], std::string, and more. A single neutral pointer erases all of them to one representation. std::span<std::byte> would force every caller to reinterpret their bytes first, and std::span<void> is ill-formed—C++ cannot express a type-agnostic buffer with span.

Capy provides const_buffer and mutable_buffer as semantically neutral buffer types with known layout.

const_buffer

const_buffer represents a contiguous region of read-only memory:

class const_buffer
{
public:
    const_buffer() = default;
    const_buffer(void const* data, std::size_t size) noexcept;
    const_buffer(mutable_buffer const& b) noexcept;  // Implicit conversion

    void const* data() const noexcept;
    std::size_t size() const noexcept;

    const_buffer& operator+=(std::size_t n) noexcept;  // Remove prefix
};

Construction

// From pointer and size
char data[] = "hello";
const_buffer buf(data, 5);

// From mutable_buffer (implicit)
mutable_buffer mbuf(data, 5);
const_buffer cbuf = mbuf;  // OK: mutable -> const

Accessors

const_buffer buf(data, 5);

void const* ptr = buf.data();  // Pointer to first byte
std::size_t len = buf.size();  // Number of bytes

Prefix Removal

The += operator removes bytes from the front of the buffer:

const_buffer buf(data, 10);

buf += 3;  // Remove first 3 bytes
// buf.data() now points 3 bytes later
// buf.size() is now 7

This is useful when processing a buffer incrementally.

mutable_buffer

mutable_buffer represents a contiguous region of writable memory:

class mutable_buffer
{
public:
    mutable_buffer() = default;
    mutable_buffer(void* data, std::size_t size) noexcept;

    void* data() const noexcept;
    std::size_t size() const noexcept;

    mutable_buffer& operator+=(std::size_t n) noexcept;
};

The interface mirrors const_buffer, but data() returns non-const void*.

Conversion

mutable_buffer implicitly converts to const_buffer:

void process(const_buffer buf);

mutable_buffer mbuf(data, size);
process(mbuf);  // OK: implicit conversion

The reverse is not allowed—you cannot implicitly convert const_buffer to mutable_buffer.

make_buffer

The make_buffer function creates buffers from various sources:

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

// From pointer and size
auto buf = make_buffer(ptr, size);

// From C array
char arr[10];
auto buf = make_buffer(arr);

// From std::array
std::array<char, 10> arr;
auto buf = make_buffer(arr);

// From std::vector
std::vector<char> vec(100);
auto buf = make_buffer(vec);

// From std::string
std::string str = "hello";
auto buf = make_buffer(str);

// From std::string_view
std::string_view sv = "hello";
auto buf = make_buffer(sv);

// From a span (std::span or boost::span)
std::span<char> sp(arr);
auto buf = make_buffer(sp);

make_buffer accepts any sized, contiguous range of trivially-copyable elements—including std::span and boost::span—in addition to the sources shown above.

The returned buffer type depends on the element constness of the range:

  • Ranges of mutable elements → mutable_buffer

  • Ranges of const elements, string_view, string literals → const_buffer

The buffer’s size, in bytes, is count * sizeof(element).

Layout Compatibility

const_buffer and mutable_buffer have the same memory layout as OS buffer structures:

  • POSIX: struct iovec { void* iov_base; size_t iov_len; }

  • Windows: struct WSABUF { ULONG len; CHAR* buf; } (note: different order)

This means conversion to OS structures is efficient—often just a reinterpret_cast for arrays of buffers.

Single Buffers as Sequences

A single buffer is a degenerate sequence—a sequence with one element. The ConstBufferSequence and MutableBufferSequence concepts recognize this:

template<ConstBufferSequence Buffers>
void write_data(Buffers const& buffers);

// All of these work:
write_data(make_buffer("hello"));         // Single buffer
write_data(std::array{buf1, buf2, buf3}); // Multiple buffers
write_data(my_composite);                  // Custom sequence

The library provides begin() and end() functions that work uniformly:

const_buffer single;
auto it = begin(single);  // Returns pointer to single
auto e = end(single);     // Returns pointer past single

std::array<const_buffer, 3> multi;
auto it = begin(multi);   // Returns multi.begin()
auto e = end(multi);      // Returns multi.end()

Reference

Header Description

<boost/capy/buffers.hpp>

Core buffer types and concepts

<boost/capy/buffers/make_buffer.hpp>

Buffer creation utilities

You have now learned about const_buffer and mutable_buffer. Continue to Buffer Sequences to understand how these types compose into sequences.