Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions doc/modules/ROOT/pages/5.buffers/5c.sequences.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -100,45 +100,47 @@ void process(Buffers const& bufs)

These functions handle both single buffers (returning pointer-to-self) and ranges (returning standard iterators).

== consuming_buffers
== buffer_slice

When transferring data incrementally, `consuming_buffers` tracks progress:
When transferring data incrementally, `buffer_slice` returns a slice that tracks progress:

[source,cpp]
----
#include <boost/capy/buffers/consuming_buffers.hpp>
#include <boost/capy/buffers/buffer_slice.hpp>

template<MutableBufferSequence Buffers>
task<std::size_t> read_all(Stream& stream, Buffers buffers)
{
consuming_buffers<Buffers> remaining(buffers);
auto remaining = buffer_slice(buffers);
std::size_t const total_size = buffer_size(buffers);
std::size_t total = 0;
while (buffer_size(remaining) > 0)

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

co_return total;
}
----

`consuming_buffers` wraps a buffer sequence and provides:
`buffer_slice(seq, offset, length)` returns an object of unspecified type that satisfies the `Slice` concept, providing:

* `consume(n)` — Mark `n` bytes as consumed (remove from front)
* Iteration over unconsumed buffers
* `buffer_size()` of remaining bytes
* `data()` — Buffer sequence view of the slice's current bytes (pass to `read_some`/`write_some`)
* `remove_prefix(n)` — Advance the start by `n` bytes

The `offset` and `length` parameters (both optional) make `buffer_slice` a general byte sub-range primitive, not just an iteration-state holder.

== Why Bidirectional?

The concepts require bidirectional ranges (not just forward ranges) for two reasons:

1. Some algorithms traverse buffers backwards
2. `consuming_buffers` needs to adjust the first buffer's start position
2. The buffer sequence view returned by `Slice::data()` needs to adjust the first and last buffers' bounds

If your custom buffer sequence only provides forward iteration, wrap it in a type that provides bidirectional access.

Expand All @@ -151,8 +153,11 @@ If your custom buffer sequence only provides forward iteration, wrap it in a typ
| `<boost/capy/buffers.hpp>`
| Concepts and iteration functions

| `<boost/capy/buffers/consuming_buffers.hpp>`
| Incremental consumption wrapper
| `<boost/capy/buffers/buffer_slice.hpp>`
| Byte sub-range slicing algorithm

| `<boost/capy/concept/slice.hpp>`
| `Slice` concept
|===

You have now learned how buffer sequences enable zero-allocation composition. Continue to xref:5.buffers/5d.system-io.adoc[System I/O Integration] to see how buffer sequences interface with operating system I/O.
26 changes: 14 additions & 12 deletions doc/modules/ROOT/pages/5.buffers/5e.algorithms.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,19 @@ The algorithm fills target buffers sequentially, reading from source buffers as
template<ReadStream Stream, MutableBufferSequence Buffers>
task<std::size_t> read_full(Stream& stream, Buffers buffers)
{
consuming_buffers<Buffers> remaining(buffers);
auto remaining = buffer_slice(buffers);
std::size_t const total_size = buffer_size(buffers);
std::size_t total = 0;
while (buffer_size(remaining) > 0)

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

co_return total;
}
----
Expand All @@ -174,18 +175,19 @@ task<std::size_t> read_full(Stream& stream, Buffers buffers)
template<WriteStream Stream, ConstBufferSequence Buffers>
task<std::size_t> write_full(Stream& stream, Buffers buffers)
{
consuming_buffers<Buffers> remaining(buffers);
auto remaining = buffer_slice(buffers);
std::size_t const total_size = buffer_size(buffers);
std::size_t total = 0;
while (buffer_size(remaining) > 0)

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

co_return total;
}
----
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/9.design/9m.WhyNotCobalt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ Capy has one `DynamicBuffer` concept. The v1/v2 split in Asio exists because of
| Yes
|

| `consuming_buffers`
| `buffer_slice`
| Yes
|

Expand Down
6 changes: 3 additions & 3 deletions doc/modules/ROOT/pages/why-capy.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Asio got buffer sequences right. The concept-driven approach—`ConstBufferSeque

Capy doesn't reinvent this. We adopt Asio's buffer sequence model because it works.

But we improve on it. Asio provides the basics; Capy extends them. Need to trim bytes from the front of a buffer sequence? Asio makes you work for it. Capy provides `slice`, `front`, `consuming_buffers`—customization points for efficient byte-level manipulation. Need a circular buffer for protocol parsing? Capy has `circular_dynamic_buffer`. Need to compose two buffers without copying? `buffer_pair`.
But we improve on it. Asio provides the basics; Capy extends them. Need to trim bytes from the front of a buffer sequence? Asio makes you work for it. Capy provides `buffer_slice` and `front`—byte-range slicing primitives for efficient byte-level manipulation. Need a circular buffer for protocol parsing? Capy has `circular_dynamic_buffer`. Need to compose two buffers without copying? `buffer_pair`.

And then there's the `DynamicBuffer` mess. If you've used Asio, you've encountered the confusing split between `DynamicBuffer_v1` and `DynamicBuffer_v2`. This exists because of a fundamental problem: when an async operation takes a buffer by value and completes via callback, who owns the buffer? The original design had flaws. The "fix" created two incompatible versions. (See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1100r0.html[P1100R0] for the full story.)

Expand All @@ -174,7 +174,7 @@ One more thing: `std::ranges` cannot help here. `ranges::size` returns the numbe

* `ConstBufferSequence`, `MutableBufferSequence`, `DynamicBuffer` — core concepts (Asio-compatible)
* `flat_dynamic_buffer`, `circular_dynamic_buffer`, `buffer_pair` — additional concrete types
* `slice`, `front`, `buffer_array`, `consuming_buffers` — byte-level manipulation utilities
* `buffer_slice`, `front`, `buffer_array` — byte-level manipulation utilities

=== Comparison

Expand Down Expand Up @@ -232,7 +232,7 @@ One more thing: `std::ranges` cannot help here. `ranges::size` returns the numbe
|
|

| `consuming_buffers`
| `buffer_slice`
|
|
|
Expand Down
4 changes: 2 additions & 2 deletions doc/unlisted/library-buffers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ task<void> echo(ReadStream auto& in, WriteStream auto& out)
| `<boost/capy/buffers/buffer_copy.hpp>`
| `buffer_copy` algorithm

| `<boost/capy/buffers/consuming_buffers.hpp>`
| Incremental buffer consumption
| `<boost/capy/buffers/buffer_slice.hpp>`
| Byte sub-range slicing algorithm

| `<boost/capy/buffers/flat_dynamic_buffer.hpp>`
| Contiguous dynamic buffer
Expand Down
3 changes: 2 additions & 1 deletion include/boost/capy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
#include <boost/capy/buffers/buffer_copy.hpp>
#include <boost/capy/buffers/buffer_pair.hpp>
#include <boost/capy/buffers/buffer_param.hpp>
#include <boost/capy/buffers/buffer_slice.hpp>
#include <boost/capy/buffers/circular_dynamic_buffer.hpp>
#include <boost/capy/buffers/consuming_buffers.hpp>
#include <boost/capy/buffers/flat_dynamic_buffer.hpp>
#include <boost/capy/buffers/front.hpp>
#include <boost/capy/buffers/make_buffer.hpp>
Expand All @@ -64,6 +64,7 @@
#include <boost/capy/concept/mutable_buffer_sequence.hpp>
#include <boost/capy/concept/read_source.hpp>
#include <boost/capy/concept/read_stream.hpp>
#include <boost/capy/concept/slice.hpp>
#include <boost/capy/concept/stream.hpp>
#include <boost/capy/concept/write_sink.hpp>
#include <boost/capy/concept/write_stream.hpp>
Expand Down
89 changes: 89 additions & 0 deletions include/boost/capy/buffers/buffer_slice.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/capy
//

#ifndef BOOST_CAPY_BUFFERS_BUFFER_SLICE_HPP
#define BOOST_CAPY_BUFFERS_BUFFER_SLICE_HPP

#include <boost/capy/detail/config.hpp>
#include <boost/capy/buffers.hpp>
#include <boost/capy/detail/slice_impl.hpp>

#include <cstddef>
#include <limits>

namespace boost {
namespace capy {

/** Return a byte-range slice of a buffer sequence.

Constructs a view over a contiguous byte range of `seq`. The
slice exposes its current bytes via `data()` (a buffer sequence)
and supports incremental consumption via `remove_prefix(n)` and
`remove_suffix(n)`.

@par Return Value
An object of unspecified type satisfying the @ref Slice concept.
Bind with `auto` and operate through the concept's members. When
`seq` models @ref MutableBufferSequence, the returned object
additionally models @ref MutableSlice.

@par Lifetime
The returned object holds a non-owning reference to data within
`seq`. `seq` must remain valid until the returned object is
destroyed. Iterators and buffer descriptors obtained through
`data()` follow the same invalidation rules as those of `seq`.

@par Parameters
@li `seq` The underlying buffer sequence.
@li `offset` Number of bytes to skip from the start of `seq`.
Clamped to `buffer_size(seq)`.
@li `length` Maximum number of bytes the slice will expose,
starting at `offset`. Clamped to `buffer_size(seq) - offset`.
Defaults to the maximum value of `std::size_t`, i.e. "to end".

@par Example
@code
template< ReadStream Stream, MutableBufferSequence MB >
task< io_result< std::size_t > >
read_all( Stream& stream, MB buffers )
{
auto s = buffer_slice( buffers );
std::size_t const total_size = buffer_size( buffers );
std::size_t total = 0;
while( total < total_size )
{
auto [ec, n] = co_await stream.read_some( s.data() );
s.remove_prefix( n );
total += n;
if( ec )
co_return {ec, total};
}
co_return {{}, total};
}
@endcode

@see Slice, MutableSlice
*/
template<class BufferSequence>
requires MutableBufferSequence<BufferSequence>
|| ConstBufferSequence<BufferSequence>
auto
buffer_slice(
BufferSequence const& seq,
std::size_t offset = 0,
std::size_t length =
(std::numeric_limits<std::size_t>::max)()) noexcept
{
return detail::slice_impl<BufferSequence>(seq, offset, length);
}

} // namespace capy
} // namespace boost

#endif
Loading
Loading