Dynamic Buffers
This page explains dynamic buffers—containers that manage resizable storage with a producer/consumer model designed for streaming I/O.
Code snippets assume using namespace boost::capy; is in effect.
|
The Producer/Consumer Model
Dynamic buffers serve as intermediate storage between a producer that writes data and a consumer that reads it. The classic example is network I/O: the network produces data that your application consumes.
Network (Producer)
|
v
+-----------------+
| Dynamic Buffer |
| [readable data] |
+-----------------+
|
v
Application (Consumer)
The buffer API reflects this model:
-
Producer side:
prepare(n)→ write data →commit(n) -
Consumer side:
data()→ read data →consume(n)
This separation prevents reading uncommitted data or overwriting unread data.
The DynamicBuffer Concept
template<class T>
concept DynamicBuffer = requires(T& buf, T const& cbuf, std::size_t n)
{
// Capacity management
{ cbuf.size() } -> std::convertible_to<std::size_t>; // Readable bytes
{ cbuf.max_size() } -> std::convertible_to<std::size_t>; // Maximum capacity
{ cbuf.capacity() } -> std::convertible_to<std::size_t>; // Current capacity
// Consumer side
{ cbuf.data() } -> ConstBufferSequence; // Get readable data
buf.consume(n); // Discard n bytes
// Producer side
{ buf.prepare(n) } -> MutableBufferSequence; // Get writable space
buf.commit(n); // Make n bytes readable
};
DynamicBufferParam for Coroutines
When passing dynamic buffers to coroutine functions, use DynamicBufferParam
with a forwarding reference:
auto read(ReadSource auto& source, DynamicBufferParam auto&& buffers)
-> task<io_result<std::size_t>>;
This concept enforces safe parameter passing:
-
Lvalues: Always allowed—the caller manages the buffer’s lifetime
-
Rvalues: Only allowed for adapter types that update external storage
Why the Distinction?
Some buffer types store bookkeeping internally:
// BAD: rvalue non-adapter loses bookkeeping
flat_dynamic_buffer buf(storage, sizeof(storage));
co_await read(source, std::move(buf)); // Compile error!
// GOOD: lvalue keeps bookkeeping accessible
flat_dynamic_buffer buf(storage, sizeof(storage));
co_await read(source, buf); // OK
Adapters update external storage, so rvalues are safe:
// GOOD: adapter rvalue, string retains data
std::string body;
co_await read(source, string_dynamic_buffer(&body)); // OK
Provided Implementations
flat_dynamic_buffer
Uses a single contiguous memory region:
#include <boost/capy/buffers/flat_dynamic_buffer.hpp>
char storage[4096];
flat_dynamic_buffer buf(storage, sizeof(storage));
// prepare() and data() always return single-element sequences
auto writable = buf.prepare(100); // Single mutable_buffer
auto readable = buf.data(); // Single const_buffer
Characteristics:
-
Data is always contiguous—good for parsers requiring linear access
-
Buffer sequences have exactly one element
-
May waste space after consume (gap at front)
Best for: Protocols requiring contiguous parsing, simple request/response patterns.
circular_dynamic_buffer
Uses a ring buffer that wraps around:
#include <boost/capy/buffers/circular_dynamic_buffer.hpp>
char storage[4096];
circular_dynamic_buffer buf(storage, sizeof(storage));
// Data may wrap around
auto readable = buf.data(); // May return const_buffer_pair
Characteristics:
-
No space wasted after consume (wraps around)
-
Buffer sequences may have two elements when data spans the wrap point
-
Fixed maximum capacity
Best for: Continuous streaming, bidirectional protocols, high-throughput scenarios.
string_dynamic_buffer
Adapts a std::string as a dynamic buffer:
#include <boost/capy/buffers/string_dynamic_buffer.hpp>
std::string body;
string_dynamic_buffer buf(&body);
// Buffer operations modify the string
co_await read(source, buf);
// body now contains the data
Characteristics:
-
Does not own storage—wraps an existing string
-
String grows as needed (up to max_size)
-
Destructor resizes string to final readable size
-
Move-only (cannot be copied)
-
Marked as adapter—safe to pass as rvalue
Best for: Building string results, HTTP bodies, text protocols.
vector_dynamic_buffer
Adapts a std::vector<unsigned char> as a dynamic buffer:
#include <boost/capy/buffers/vector_dynamic_buffer.hpp>
std::vector<unsigned char> data;
vector_dynamic_buffer buf(&data);
co_await read(source, buf);
// data now contains the bytes
Characteristics:
-
Similar to string_dynamic_buffer but for binary data
-
Vector grows as needed
-
Marked as adapter—safe to pass as rvalue
Best for: Binary protocols, file I/O, binary message bodies.
Real-World Examples
Reading HTTP Body
task<std::string> read_body(ReadSource auto& source, std::size_t content_length)
{
std::string body;
string_dynamic_buffer buf(&body, content_length);
// Read until we have the full body
char temp[4096];
std::size_t remaining = content_length;
while (remaining > 0)
{
auto to_read = (std::min)(remaining, sizeof(temp));
auto [ec, n] = co_await source.read(mutable_buffer(temp, to_read));
if (ec.failed())
co_return {};
auto writable = buf.prepare(n);
buffer_copy(writable, const_buffer(temp, n));
buf.commit(n);
remaining -= n;
}
co_return body;
}
Streaming Protocol Parser
task<void> parse_stream(ReadStream auto& stream)
{
char storage[8192];
circular_dynamic_buffer buf(storage, sizeof(storage));
for (;;)
{
// Read more data
auto [ec, n] = co_await stream.read_some(buf.prepare(1024));
if (ec == cond::eof)
break;
if (ec.failed())
co_return;
buf.commit(n);
// Parse complete messages
while (auto msg = try_parse_message(buf.data()))
{
handle_message(*msg);
buf.consume(msg->size());
}
}
}
Choosing a Buffer Type
| Type | Best For | Trade-off |
|---|---|---|
|
Protocols requiring contiguous data |
May waste space after consume |
|
High-throughput streaming |
Two-element sequences add complexity |
|
Building string results |
Requires external string ownership |
|
Binary data accumulation |
Requires external vector ownership |
Summary
| Component | Purpose |
|---|---|
|
Concept for resizable buffers with prepare/commit semantics |
|
Safe parameter passing constraint for coroutines |
|
Contiguous storage, single-element sequences |
|
Ring buffer, no space waste, may have two-element sequences |
|
Adapter for |
|
Adapter for |