Buffer Sequences
This section explains buffer sequences—the concept that enables zero-allocation composition of buffers.
Prerequisites
-
Completed Buffer Types
-
Understanding of
const_bufferandmutable_buffer
What Is a Buffer Sequence?
A buffer sequence is any type that can produce an iteration of buffers. Formally:
-
A single buffer (like
const_buffer) is a sequence of one element -
A range of buffers (like
vector<const_buffer>) is a multi-element sequence -
Any bidirectional range with buffer-convertible values qualifies
The Concepts
ConstBufferSequence
template<typename T>
concept ConstBufferSequence =
std::is_convertible_v<T, const_buffer> || (
std::ranges::bidirectional_range<T> &&
std::is_convertible_v<std::ranges::range_value_t<T>, const_buffer>);
A type satisfies ConstBufferSequence if:
-
It converts to
const_bufferdirectly (single buffer), OR -
It is a bidirectional range whose elements convert to
const_buffer
Satisfying the Concepts
Many common types satisfy these concepts:
// Single buffers
const_buffer cb; // ConstBufferSequence
mutable_buffer mb; // MutableBufferSequence (and ConstBufferSequence)
// Standard containers of buffers
std::vector<const_buffer> v; // ConstBufferSequence
std::array<mutable_buffer, 3> a; // MutableBufferSequence
// String types (convert to single buffer)
std::string str; // ConstBufferSequence (via make_buffer)
std::string_view sv; // ConstBufferSequence
Heterogeneous Composition
Because the concept accepts anything convertible to buffer, you can mix types:
template<ConstBufferSequence Buffers>
void send(Buffers const& bufs);
// All of these work:
send(make_buffer("Hello")); // string literal
send(std::string_view{"Hello"}); // string_view
send(std::array{buf1, buf2}); // array of buffers
send(my_custom_buffer_sequence); // custom type
Iterating Buffer Sequences
Use begin() and end() from <boost/capy/buffers.hpp>:
template<ConstBufferSequence Buffers>
void process(Buffers const& bufs)
{
for (auto it = begin(bufs); it != end(bufs); ++it)
{
const_buffer buf = *it;
// Process buf.data(), buf.size()
}
}
These functions handle both single buffers (returning pointer-to-self) and ranges (returning standard iterators).
consuming_buffers
When transferring data incrementally, consuming_buffers tracks progress:
#include <boost/capy/buffers/consuming_buffers.hpp>
template<MutableBufferSequence Buffers>
task<std::size_t> read_all(Stream& stream, Buffers buffers)
{
consuming_buffers<Buffers> remaining(buffers);
std::size_t total = 0;
while (buffer_size(remaining) > 0)
{
auto [ec, n] = co_await stream.read_some(remaining);
remaining.consume(n);
total += n;
if (ec)
break;
}
co_return total;
}
consuming_buffers wraps a buffer sequence and provides:
-
consume(n)— Marknbytes as consumed (remove from front) -
Iteration over unconsumed buffers
-
buffer_size()of remaining bytes
Why Bidirectional?
The concepts require bidirectional ranges (not just forward ranges) for two reasons:
-
Some algorithms traverse buffers backwards
-
consuming_buffersneeds to adjust the first buffer’s start position
If your custom buffer sequence only provides forward iteration, wrap it in a type that provides bidirectional access.
Reference
| Header | Description |
|---|---|
|
Concepts and iteration functions |
|
Incremental consumption wrapper |
You have now learned how buffer sequences enable zero-allocation composition. Continue to System I/O Integration to see how buffer sequences interface with operating system I/O.