diff --git a/doc/lint/baseline.json b/doc/lint/baseline.json index 3a6b2f679..dd90586ad 100644 --- a/doc/lint/baseline.json +++ b/doc/lint/baseline.json @@ -95,11 +95,6 @@ "lint/.docstrings/buffers.hpp.adoc:#4:Vale.Spelling", "lint/.docstrings/buffers.hpp.adoc:#5:Vale.Spelling", "lint/.docstrings/buffers.hpp.adoc:#6:Vale.Spelling", - "lint/.docstrings/buffers/buffer_param.hpp.adoc:#1:Vale.Spelling", - "lint/.docstrings/buffers/buffer_slice.hpp.adoc:#1:Vale.Spelling", - "lint/.docstrings/buffers/buffer_slice.hpp.adoc:#2:Vale.Spelling", - "lint/.docstrings/buffers/consuming_buffers.hpp.adoc:#1:Vale.Spelling", - "lint/.docstrings/buffers/consuming_buffers.hpp.adoc:#2:Vale.Spelling", "lint/.docstrings/buffers/make_buffer.hpp.adoc:#1:Google.LyHyphens", "lint/.docstrings/buffers/make_buffer.hpp.adoc:#1:Google.OxfordComma", "lint/.docstrings/buffers/make_buffer.hpp.adoc:#2:Google.LyHyphens", @@ -481,12 +476,12 @@ "?:#6:unsupported HTML tag ", "?:#7:unsupported HTML tag ", "?:#8:unsupported HTML tag ", - "include/boost/capy/buffers.hpp:#1:begin: variable is undocumented", - "include/boost/capy/buffers.hpp:#1:buffer_empty: variable is undocumented", + "include/boost/capy/buffers.hpp:#1:buffer_sequence_begin: variable is undocumented", + "include/boost/capy/buffers.hpp:#1:buffer_sequence_end: variable is undocumented", "include/boost/capy/buffers.hpp:#1:buffer_size: variable is undocumented", - "include/boost/capy/buffers.hpp:#1:end: variable is undocumented", + "include/boost/capy/buffers.hpp:#1:buffers_empty: variable is undocumented", "include/boost/capy/buffers/buffer_copy.hpp:#1:buffer_copy: variable is undocumented", - "include/boost/capy/buffers/front.hpp:#1:front: variable is undocumented", + "include/boost/capy/buffers/buffers_front.hpp:#1:buffers_front: variable is undocumented", "include/boost/capy/detail/intrusive.hpp:#1:boost::capy::detail::intrusive_list: Failed to resolve reference to 'node'", "include/boost/capy/ex/executor_ref.hpp:#1:executor_ref: function is undocumented", "include/boost/capy/ex/recycling_memory_resource.hpp:#1:boost::capy::recycling_memory_resource::allocate_fast: 2nd parameter is unnamed", diff --git a/doc/modules/ROOT/pages/4.coroutines/4g.composition.adoc b/doc/modules/ROOT/pages/4.coroutines/4g.composition.adoc index 9a8261414..7fcff9c16 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4g.composition.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4g.composition.adoc @@ -144,10 +144,8 @@ loop that bound it. In the snippet above nothing dangles: `items` outlives the cpp:when_all[]. The hazard is what a caller could do -- passing a temporary would leave the stored -task holding a reference to it. Awaiting a task immediately, as function `read_all` -from section -xref:5.buffers/5d.buffer-algo.adoc#_cursor_interface[Buffer Algorithms] does, -is the case where a reference parameter is safe. +task holding a reference to it. Awaiting a task immediately is the case where a +reference parameter is safe. ==== === Racing a Deadline diff --git a/doc/modules/ROOT/pages/5.buffers/5.intro.adoc b/doc/modules/ROOT/pages/5.buffers/5.intro.adoc index 3bc2f74db..a6f3e0e78 100644 --- a/doc/modules/ROOT/pages/5.buffers/5.intro.adoc +++ b/doc/modules/ROOT/pages/5.buffers/5.intro.adoc @@ -29,8 +29,8 @@ designed with the following criteria in mind: that describe sequences of those handles. * xref:5.buffers/5c.system-io-integration.adoc[System I/O Integration] -- how a sequence maps onto the structures that native I/O systems expect. -* xref:5.buffers/5d.buffer-algo.adoc[Buffer Algorithms] -- counting bytes, - slicing, copying, and consuming those sequences. +* xref:5.buffers/5d.buffer-algo.adoc[Buffer Algorithms] -- counting bytes + and copying those sequences. * xref:5.buffers/5e.rationale.adoc[Rationale] -- why the handles use `void*`, why sequences are concepts, and why those concepts require bidirectional iteration. diff --git a/doc/modules/ROOT/pages/5.buffers/5b.composite-buffers.adoc b/doc/modules/ROOT/pages/5.buffers/5b.composite-buffers.adoc index 9463048d5..6fbee2b18 100644 --- a/doc/modules/ROOT/pages/5.buffers/5b.composite-buffers.adoc +++ b/doc/modules/ROOT/pages/5.buffers/5b.composite-buffers.adoc @@ -135,7 +135,7 @@ Algorithms call them inside `noexcept` functions, so a conversion that throws te Header `` also provides a couple of function objects for navigating buffer sequences. In order to iterate over contiguous buffers themselves (as opposed to bytes), -use cpp:begin[capy::begin] and cpp:end[capy::end]: +use cpp:buffer_sequence_begin[] and cpp:buffer_sequence_end[]: [source,cpp] ---- @@ -150,10 +150,27 @@ These functions treat single contiguous buffers as a one-element range of contig include::example$snippets/5b_composite_buffers.cpp[tag=begin_end_uniform,indent=0] ---- -Function cpp:buffer_length[] says how many contiguous buffers are present in the buffer +Function cpp:buffers_count[] says how many contiguous buffers are present in the buffer sequence. It is equivalent to: [source,cpp] ---- include::example$snippets/5b_composite_buffers.cpp[tag=length_equivalent,indent=0] ---- + +=== Iterating Over Bytes + +To treat a buffer sequence as one run of bytes, use cpp:buffers_begin[] and +cpp:buffers_end[]. They return a cpp:buffers_iterator[], a random access +iterator that crosses buffer boundaries and skips empty buffers, so standard +algorithms can scan the data without copying it into contiguous storage: + +[source,cpp] +---- +include::example$snippets/5b_composite_buffers.cpp[tag=buffers_iterator_include] + +include::example$snippets/5b_composite_buffers.cpp[tag=iterate_bytes,indent=0] +---- + +Moving the iterator by `n` bytes walks the buffers it crosses, so jumps are +linear in the number of buffers. The sequence must outlive the iterators. diff --git a/doc/modules/ROOT/pages/5.buffers/5c.system-io-integration.adoc b/doc/modules/ROOT/pages/5.buffers/5c.system-io-integration.adoc index 5175e8cb1..e9d273e35 100644 --- a/doc/modules/ROOT/pages/5.buffers/5c.system-io-integration.adoc +++ b/doc/modules/ROOT/pages/5.buffers/5c.system-io-integration.adoc @@ -55,8 +55,8 @@ auto platform_write(Buffers const& buffers) { iovec iovecs[16]; // fixed on-frame window, never heap-allocated - auto it = begin(buffers); - auto last = end(buffers); + auto it = capy::buffer_sequence_begin(buffers); + auto last = capy::buffer_sequence_end(buffers); while (it != last) { std::size_t count = fill_iovecs(iovecs, it, last, 16); // up to 16 diff --git a/doc/modules/ROOT/pages/5.buffers/5d.buffer-algo.adoc b/doc/modules/ROOT/pages/5.buffers/5d.buffer-algo.adoc index ca67aaa1f..d4468af97 100644 --- a/doc/modules/ROOT/pages/5.buffers/5d.buffer-algo.adoc +++ b/doc/modules/ROOT/pages/5.buffers/5d.buffer-algo.adoc @@ -21,7 +21,7 @@ include::example$snippets/5d_buffer_algo.cpp[tag=buffer_size_example,indent=0] ---- -cpp:buffer_empty[] reports whether a sequence carries no data, +cpp:buffers_empty[] reports whether a sequence carries no data, either because it holds no buffers or because every buffer has size zero. It is a shorthand for `buffer_size(bufseq) == 0`. @@ -43,9 +43,9 @@ This would translate into later using a zero-size buffer. The following two functions have similar names but return different values: * cpp:buffer_size[] -- number of bytes across the entire buffer sequence, - * cpp:buffer_length[] -- number of contiguous buffers in the sequence. + * cpp:buffers_count[] -- number of contiguous buffers in the sequence. -A three-buffer sequence of 100 bytes each has a cpp:buffer_size[] of 300 and a cpp:buffer_length[] of 3. +A three-buffer sequence of 100 bytes each has a cpp:buffer_size[] of 300 and a cpp:buffers_count[] of 3. ==== @@ -82,49 +82,7 @@ include::example$snippets/5d_buffer_algo.cpp[tag=buffer_copy_cross,indent=0] The algorithm fills target buffers in order, reading from source buffers as needed. It handles a source buffer spanning several targets, and the reverse. - -== Slicing - -cpp:buffer_slice[] returns a byte sub-range of a buffer sequence, as a value: - -[source,cpp] ----- -include::example$snippets/5d_buffer_algo.cpp[tag=buffer_slice_include] - -include::example$snippets/5d_buffer_algo.cpp[tag=buffer_slice,indent=0] ----- - -cpp:buffer_slice[]`(seq, offset, length)` returns a value that is itself a buffer sequence, so you can pass it to any operation expecting one. Both `offset` and `length` are optional, which makes it a general byte sub-range primitive. Except in the single-buffer case the result borrows `seq`, so the sequence must outlive the slice. - -Capy creates and manages buffer handles on your behalf. Those are the sub-range a cpp:buffer_slice[] produces, and the descriptors a type-erased stream passes to the OS. Each is valid only for the window its API documents. - -== Cursor Interface - -When transferring data incrementally, cpp:consuming_buffers[] is a cursor that tracks progress: - -[source,cpp] ----- -include::example$snippets/5d_buffer_algo.cpp[tag=consuming_buffers_include] - -include::example$snippets/5d_buffer_algo.cpp[tag=read_all,indent=0] ----- - -The cursor borrows the underlying sequence and provides: - -* cpp:consuming_buffers::data[data]`()` -- buffer sequence view of the remaining bytes -* cpp:consuming_buffers::consume[consume]`(n)` -- advance past `n` transferred bytes, in place - -[NOTE] -==== -`read_all` takes its stream by reference and its buffers by value. Both are safe -here, because the caller owns the stream and awaits the task immediately. - -A coroutine reads its parameters when its body runs, not when the call is -written. A task that is stored and awaited later outlives its call expression, -so a reference parameter can dangle by then. That is why the fan-out example on -xref:4.coroutines/4g.composition.adoc[Concurrent Composition] takes its item by -value: it collects its tasks first, then awaits them together. -==== +Capy creates and manages buffer handles on your behalf, such as the descriptors a type-erased stream passes to the OS. Each is valid only for the window its API documents. == Custom Buffer Types diff --git a/doc/modules/ROOT/pages/5.buffers/5e.rationale.adoc b/doc/modules/ROOT/pages/5.buffers/5e.rationale.adoc index 3b987e933..7211c6c81 100644 --- a/doc/modules/ROOT/pages/5.buffers/5e.rationale.adoc +++ b/doc/modules/ROOT/pages/5.buffers/5e.rationale.adoc @@ -38,4 +38,4 @@ At type-erasure boundaries the tradeoff reverses. Virtual functions need concret The concepts require bidirectional ranges, not merely forward ranges, for two reasons: 1. Some algorithms traverse buffers backwards -2. The slice views from cpp:buffer_slice[] and cpp:consuming_buffers::data[]`()` must adjust the first and last buffers' bounds +2. Composed operations such as cpp:read[] and cpp:write[] resume partway through a sequence, through a view that must adjust the first buffer's bounds diff --git a/doc/modules/ROOT/pages/8.examples/8c.buffer-composition.adoc b/doc/modules/ROOT/pages/8.examples/8c.buffer-composition.adoc index 6afd22903..d6bc900bf 100644 --- a/doc/modules/ROOT/pages/8.examples/8c.buffer-composition.adoc +++ b/doc/modules/ROOT/pages/8.examples/8c.buffer-composition.adoc @@ -51,7 +51,7 @@ Capy's buffer-sequence concepts accept any range of cpp:const_buffer[] or cpp:mu include::example$buffer-composition/buffer_composition.cpp[tag=multi_buffer,indent=0] ---- -For more than two buffers, the same pattern works with a larger `std::array`. Buffer sequences support `buffer_size()` and `buffer_length()` for querying total bytes and buffer count. +For more than two buffers, the same pattern works with a larger `std::array`. Buffer sequences support `buffer_size()` and `buffers_count()` for querying total bytes and buffer count. === Scatter/Gather I/O diff --git a/doc/modules/ROOT/pages/9.design/9b.Separation.adoc b/doc/modules/ROOT/pages/9.design/9b.Separation.adoc index c464e058d..fe834ea24 100644 --- a/doc/modules/ROOT/pages/9.design/9b.Separation.adoc +++ b/doc/modules/ROOT/pages/9.design/9b.Separation.adoc @@ -143,7 +143,7 @@ Corosio contains five platform backends, each a substantial body of platform-spe * *kqueue* on macOS and BSD * *select* as a POSIX fallback -Merging these into Capy would mean that a developer who wants a cpp:task[task<>] type or a cpp:buffer_slice[] must compile against platform I/O headers. Keeping Capy separate ensures that none of the headers a consumer includes transitively pull in anything from the platform I/O layer. +Merging these into Capy would mean that a developer who wants a cpp:task[task<>] type or a cpp:buffer_copy[] must compile against platform I/O headers. Keeping Capy separate ensures that none of the headers a consumer includes transitively pull in anything from the platform I/O layer. == Conclusion diff --git a/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc b/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc index 93939e164..add534a21 100644 --- a/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc +++ b/doc/modules/ROOT/pages/9.design/9c.ReadStream.adoc @@ -80,7 +80,7 @@ Deferral is forced only at the boundary, when the final bytes exactly fill the b === The Empty-Buffer Rule -When `buffer_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. +When `buffers_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. Whether the implementation performs a system call for a zero-length buffer is unspecified. A concrete type that short-circuits with `(!ec, 0)` conforms. A concrete type that forwards the zero-length call to the OS and reports whatever condition arises also conforms. diff --git a/doc/modules/ROOT/pages/9.design/9m.WhyNotAsio.adoc b/doc/modules/ROOT/pages/9.design/9m.WhyNotAsio.adoc index 6a34d0870..b631e2ee7 100644 --- a/doc/modules/ROOT/pages/9.design/9m.WhyNotAsio.adoc +++ b/doc/modules/ROOT/pages/9.design/9m.WhyNotAsio.adoc @@ -88,34 +88,45 @@ Asio got xref:glossary.adoc#buffer-sequence[buffer sequences] right. The concept We adopt Asio's buffer sequence model because it works. -But we improve on it. Need to trim bytes from the front of a buffer sequence? Asio makes you work for it. Capy provides cpp:buffer_slice[] and cpp:front[]—byte-range slicing primitives for efficient byte-level manipulation. Need to compose two buffers without copying? Use `std::array` (or any range of buffers) directly — Capy's buffer-sequence concepts accept arbitrary ranges. +But we improve on it. Capy states the requirements as C++20 concepts rather than type traits, and cpp:MutableBufferSequence[] refines cpp:ConstBufferSequence[]. A function overloaded on both selects the mutable overload for a mutable sequence instead of being ambiguous. Capy also adds small algorithms that Asio leaves out: cpp:buffers_front[], cpp:buffers_count[] and cpp:buffers_empty[]. `std::ranges` cannot help here. `ranges::size` returns the number of buffers, not the total bytes. Range views can drop entire elements, but buffer sequences need byte-level trimming. Buffer sequences need their own concepts. === What Capy Offers * cpp:ConstBufferSequence[], cpp:MutableBufferSequence[] — core concepts (Asio-compatible) -* cpp:buffer_slice[], cpp:front[] — byte-level manipulation utilities +* cpp:buffers_front[] — the first buffer of a sequence +* cpp:buffers_count[] — the number of buffers in a sequence +* cpp:buffers_empty[] — whether a sequence holds any bytes === Comparison -Capy adopts cpp:ConstBufferSequence[], cpp:MutableBufferSequence[], cpp:const_buffer[] and cpp:mutable_buffer[] from Asio. Its element requirement is stricter: an element of a mutable buffer sequence must also convert to cpp:const_buffer[]. Every mutable buffer sequence is therefore also a const buffer sequence. What Capy adds is byte-level slicing: +Capy adopts cpp:ConstBufferSequence[], cpp:MutableBufferSequence[], cpp:const_buffer[] and cpp:mutable_buffer[] from Asio. Its element requirement is stricter: an element of a mutable buffer sequence must also convert to cpp:const_buffer[]. Every mutable buffer sequence is therefore also a const buffer sequence. The buffer algorithms compare as follows: [cols="1,1"] |=== | Capy | Asio -| cpp:buffer_slice[] +| cpp:buffer_sequence_begin[], cpp:buffer_sequence_end[] +| `buffer_sequence_begin`, `buffer_sequence_end` + +| cpp:buffers_begin[], cpp:buffers_end[] +| `buffers_begin`, `buffers_end` + +| cpp:buffers_front[] +^| - + +| cpp:buffers_count[] ^| - -| cpp:front[] +| cpp:buffers_empty[] ^| - +| cpp:buffer_size[] +| `buffer_size` + | cpp:buffer_copy[] | `buffer_copy` - -| Byte-level trimming -^| - |=== == Coroutine Execution Model diff --git a/doc/modules/ROOT/pages/9.design/9n.WhyNotCobalt.adoc b/doc/modules/ROOT/pages/9.design/9n.WhyNotCobalt.adoc index 25cd07055..aba9b4dfb 100644 --- a/doc/modules/ROOT/pages/9.design/9n.WhyNotCobalt.adoc +++ b/doc/modules/ROOT/pages/9.design/9n.WhyNotCobalt.adoc @@ -295,15 +295,7 @@ Cobalt does not provide buffer sequence types. Users who need these features use | Yes | Via Asio -| cpp:front[] -| Yes -| - -| cpp:buffer_slice[] -| Yes -| - -| Byte-level trimming +| cpp:buffers_front[] | Yes | |=== @@ -531,7 +523,7 @@ Every `co_await ws.write(...)` call creates a xref:glossary.adoc#coroutine-frame | `cancellation_signal`, automatic, OS-level | Buffer sequences -| Extended (cpp:buffer_slice[], cpp:front[]) +| Extended (cpp:buffers_front[]) | None (use Asio directly) | Allocator control diff --git a/doc/modules/ROOT/pages/9.design/9p.WhyNotTMC.adoc b/doc/modules/ROOT/pages/9.design/9p.WhyNotTMC.adoc index 2d0b2aa74..c0d705da5 100644 --- a/doc/modules/ROOT/pages/9.design/9p.WhyNotTMC.adoc +++ b/doc/modules/ROOT/pages/9.design/9p.WhyNotTMC.adoc @@ -119,8 +119,7 @@ NOTE: Neither library prevents the caller from initiating multiple concurrent I/ *Capy provides:* * xref:glossary.adoc#buffer-sequence[Buffer sequences] (xref:glossary.adoc#scatter-gather-io[scatter/gather I/O] without copying) -* Algorithms: slice, copy, concatenate, consume -* Dynamic buffers that grow as needed +* Algorithms: copy, count, and inspect buffer sequences * xref:glossary.adoc#type-erased[Type-erased] streams: write code once, use with any stream type *TMC provides:* diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index ef518c8ca..2bacbf45c 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -17,19 +17,14 @@ exclude-symbols: - 'boost::capy::test::blocking_handler_wrapper' - 'boost::capy::test::run_blocking_wrapper' - 'boost::capy::test::bufgrind::next_awaitable' -# Private members of buffer_param's anonymous union. `inaccessible-members: -# never` does not reach variant members of an anonymous union in this MrDocs -# build, so these two private fields leak into the reference as "[variant -# member]" rows. They are storage internals, not API. The `**` spans the -# unnamed union's own scope, which sits between the class and the members. - - 'boost::capy::buffer_param::**::dummy_' - - 'boost::capy::buffer_param::**::arr_' -# Same anonymous-union leak in the task and quitter promises: `ep_` is the -# exception_ptr held in a union so it is not constructed or destroyed -# automatically, and it sits in a `private:` section that `inaccessible-members: -# never` would otherwise filter. Its sibling `has_ep_`/`state_` -- private but -# not in a union -- is correctly filtered, which is what identifies the union as -# the cause. +# Private members of anonymous unions. `inaccessible-members: never` does not +# reach variant members of an anonymous union in this MrDocs build, so they +# leak into the reference as "[variant member]" rows. In the task and quitter +# promises, `ep_` is the exception_ptr held in a union so it is not constructed +# or destroyed automatically. Its sibling `has_ep_`/`state_` -- private but not +# in a union -- is correctly filtered, which is what identifies the union as +# the cause. The `**` spans the unnamed union's own scope, which sits between +# the class and the members. - 'boost::capy::task::**::ep_' - 'boost::capy::quitter::**::ep_' implementation-defined: @@ -64,7 +59,7 @@ warn-if-doc-error: true # Two further MrDocs 0.8.0 limitations stay grandfathered in # doc/lint/baseline.json instead of being fixed here. The reasoning lives next # to the affected code so it is findable from either end: -# include/boost/capy/buffers/front.hpp -- the six buffer CPO +# include/boost/capy/buffers/buffers_front.hpp -- the six buffer CPO # "variable is undocumented" findings # include/boost/capy/ex/run.hpp -- "run: Documented parameter 'alloc' # does not exist" diff --git a/doc/unlisted/library-buffers.adoc b/doc/unlisted/library-buffers.adoc index 21bfb9372..3768b80c8 100644 --- a/doc/unlisted/library-buffers.adoc +++ b/doc/unlisted/library-buffers.adoc @@ -63,7 +63,7 @@ task echo(ReadStream auto& in, WriteStream auto& out) | Scatter/gather I/O with multiple buffers | xref:../buffers/algorithms.adoc[Buffer Algorithms] -| `buffer_copy`, `buffer_size`, `buffer_empty` +| `buffer_copy`, `buffer_size`, `buffers_empty` |=== == Key Types @@ -107,8 +107,8 @@ task echo(ReadStream auto& in, WriteStream auto& out) | `` | `buffer_copy` algorithm -| `` -| Byte sub-range slicing algorithm +| `` +| `buffers_iterator`, `buffers_begin`, `buffers_end` |=== == Navigation diff --git a/doc/unlisted/library-streams.adoc b/doc/unlisted/library-streams.adoc index 741629fef..f08007097 100644 --- a/doc/unlisted/library-streams.adoc +++ b/doc/unlisted/library-streams.adoc @@ -126,7 +126,7 @@ If `buffer_size(buffers) > 0`: * If `!ec`: `n >= 1 && n \<= buffer_size(buffers)`. `n` bytes were read. * If `ec`: `n >= 0 && n \<= buffer_size(buffers)`. `n` is the number of bytes read before the I/O condition arose. -If `buffer_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. +If `buffers_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. ==== Example @@ -180,7 +180,7 @@ If `buffer_size(buffers) > 0`: * If `!ec`: `n >= 1 && n \<= buffer_size(buffers)`. `n` bytes were written. * If `ec`: `n >= 0 && n \<= buffer_size(buffers)`. `n` is the number of bytes written before the I/O condition arose. -If `buffer_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. +If `buffers_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. ==== Example diff --git a/example/buffer-composition/buffer_composition.cpp b/example/buffer-composition/buffer_composition.cpp index 90356b806..68c91d4e6 100644 --- a/example/buffer-composition/buffer_composition.cpp +++ b/example/buffer-composition/buffer_composition.cpp @@ -55,7 +55,7 @@ void demonstrate_two_buffer_scatter() // Calculate total size std::size_t total = capy::buffer_size(message); std::cout << "Total message size: " << total << " bytes\n"; - std::cout << "Buffer count: " << capy::buffer_length(message) << "\n"; + std::cout << "Buffer count: " << capy::buffers_count(message) << "\n"; // Iterate through buffers std::cout << "\nBuffer contents:\n"; @@ -90,7 +90,7 @@ void demonstrate_buffer_array() std::size_t total = capy::buffer_size(http_response); std::cout << "HTTP response size: " << total << " bytes\n"; - std::cout << "Buffer count: " << capy::buffer_length(http_response) << "\n"; + std::cout << "Buffer count: " << capy::buffers_count(http_response) << "\n"; // In real code with streams: // co_await write(stream, http_response); @@ -110,7 +110,7 @@ void demonstrate_mutable_buffers() capy::mutable_buffer(buf2, sizeof(buf2)) }}; - std::cout << "Prepared " << capy::buffer_length(recv_buffers) + std::cout << "Prepared " << capy::buffers_count(recv_buffers) << " buffers with " << capy::buffer_size(recv_buffers) << " bytes total capacity\n"; diff --git a/example/cuda/datamovement/cuda_datamovement.hpp b/example/cuda/datamovement/cuda_datamovement.hpp index 6af4f21d3..b00fc52e8 100644 --- a/example/cuda/datamovement/cuda_datamovement.hpp +++ b/example/cuda/datamovement/cuda_datamovement.hpp @@ -330,8 +330,9 @@ class cuda_device_stream { // Enqueue the whole sequence before the host function so // the stream runs the batch back to back. - auto const end = capy::end(buffers); - for(auto it = capy::begin(buffers); it != end; ++it) + auto const end = capy::buffer_sequence_end(buffers); + for(auto it = capy::buffer_sequence_begin(buffers); + it != end; ++it) { const_buffer b = *it; auto err = cudaMemcpyAsync( diff --git a/include/boost/capy.hpp b/include/boost/capy.hpp index a27e2caab..5429efffd 100644 --- a/include/boost/capy.hpp +++ b/include/boost/capy.hpp @@ -35,10 +35,8 @@ // Buffers #include #include -#include -#include -#include -#include +#include +#include #include // Concepts diff --git a/include/boost/capy/buffers.hpp b/include/boost/capy/buffers.hpp index 7ec8df028..b6876e40f 100644 --- a/include/boost/capy/buffers.hpp +++ b/include/boost/capy/buffers.hpp @@ -351,7 +351,7 @@ constexpr struct { return std::ranges::begin(bs); } -} begin {}; +} buffer_sequence_begin {}; /** Return an iterator past the last buffer in a sequence. @@ -396,7 +396,7 @@ constexpr struct { return std::ranges::end(bs); } -} end {}; +} buffer_sequence_end {}; /** Return the total byte count across all buffers in a sequence. @@ -417,7 +417,7 @@ constexpr struct /** Return the total byte count across all buffers in a sequence. Sums the `size()` of each buffer in the sequence. This differs - from `buffer_length` which counts the number of buffer elements. + from `buffers_count` which counts the number of buffer elements. @param bs The buffer sequence. @@ -432,8 +432,8 @@ constexpr struct CB const& bs) const noexcept { std::size_t n = 0; - auto const e = capy::end(bs); - for(auto it = capy::begin(bs); it != e; ++it) + auto const e = capy::buffer_sequence_end(bs); + for(auto it = capy::buffer_sequence_begin(bs); it != e; ++it) n += const_buffer(*it).size(); return n; } @@ -464,8 +464,8 @@ constexpr struct constexpr bool operator()( CB const& bs) const noexcept { - auto it = begin(bs); - auto const end_ = end(bs); + auto it = buffer_sequence_begin(bs); + auto const end_ = buffer_sequence_end(bs); while(it != end_) { const_buffer b(*it++); @@ -477,7 +477,7 @@ constexpr struct #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif -} buffer_empty {}; +} buffers_empty {}; namespace detail { @@ -508,7 +508,7 @@ length_impl(It first, It last, long) Counts the number of individual buffer objects, not bytes. For a single buffer, returns 1. For a range, returns the - distance from `begin` to `end`. + distance from `buffer_sequence_begin` to `buffer_sequence_end`. @param bs The buffer sequence. @@ -518,10 +518,10 @@ length_impl(It first, It last, long) */ template std::size_t -buffer_length(CB const& bs) +buffers_count(CB const& bs) { return detail::length_impl( - begin(bs), end(bs), 0); + buffer_sequence_begin(bs), buffer_sequence_end(bs), 0); } /// Names `mutable_buffer` for a mutable sequence, `const_buffer` otherwise. diff --git a/include/boost/capy/buffers/asio.hpp b/include/boost/capy/buffers/asio.hpp index a2b8be26c..eb2075bbc 100644 --- a/include/boost/capy/buffers/asio.hpp +++ b/include/boost/capy/buffers/asio.hpp @@ -67,7 +67,7 @@ class buffer_sequence_adaptor if constexpr (native_asio) return asio::buffer_sequence_begin(bs); else - return capy::begin(bs); + return capy::buffer_sequence_begin(bs); } using source_iterator = decltype( @@ -243,7 +243,7 @@ class buffer_sequence_adaptor asio::buffer_sequence_begin(bs_)); else return const_iterator( - capy::begin(bs_)); + capy::buffer_sequence_begin(bs_)); } const_iterator @@ -254,7 +254,7 @@ class buffer_sequence_adaptor asio::buffer_sequence_end(bs_)); else return const_iterator( - capy::end(bs_)); + capy::buffer_sequence_end(bs_)); } private: diff --git a/include/boost/capy/buffers/buffer_copy.hpp b/include/boost/capy/buffers/buffer_copy.hpp index 166747d28..acfde9968 100644 --- a/include/boost/capy/buffers/buffer_copy.hpp +++ b/include/boost/capy/buffers/buffer_copy.hpp @@ -52,10 +52,10 @@ constexpr struct std::size_t total = 0; std::size_t pos0 = 0; std::size_t pos1 = 0; - auto const end0 = end(src); - auto const end1 = end(dest); - auto it0 = begin(src); - auto it1 = begin(dest); + auto const end0 = buffer_sequence_end(src); + auto const end1 = buffer_sequence_end(dest); + auto it0 = buffer_sequence_begin(src); + auto it1 = buffer_sequence_begin(dest); while( total < at_most && it0 != end0 && diff --git a/include/boost/capy/buffers/buffer_param.hpp b/include/boost/capy/buffers/buffer_param.hpp deleted file mode 100644 index c6e2aec84..000000000 --- a/include/boost/capy/buffers/buffer_param.hpp +++ /dev/null @@ -1,230 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) -// Copyright (c) 2026 Michael Vandeberg -// Copyright (c) 2026 Steve Gerbino -// -// 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 -// - -/* - COROUTINE BUFFER SEQUENCE LIFETIME REQUIREMENT - =============================================== - Buffer sequence parameters in coroutine APIs MUST be passed BY VALUE, - never by reference. When a coroutine suspends, reference parameters may - dangle if the caller's object goes out of scope before resumption. - - CORRECT: task<> read_some(MutableBufferSequence auto buffers) - WRONG: task<> read_some(MutableBufferSequence auto& buffers) - WRONG: task<> read_some(MutableBufferSequence auto const& buffers) - - The buffer_param class works with this model: it takes a const& in its - constructor (for the non-coroutine scope) but the caller's template - function accepts the buffer sequence by value, ensuring the sequence - lives in the coroutine frame. -*/ - -#ifndef BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP -#define BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP - -#include -#include - -#include -#include -#include - -namespace boost { -namespace capy { - -/** A buffer sequence wrapper providing windowed access. - - This template class wraps any buffer sequence and provides - incremental access through a sliding window of buffer - descriptors. It handles both const and mutable buffer - sequences automatically. - - @par Coroutine Lifetime Requirement - - When used in coroutine APIs, the outer template function - MUST accept the buffer sequence parameter BY VALUE: - - @par !example example_1 - - - Pass-by-value ensures the buffer sequence is copied into - the coroutine frame and remains valid across suspension - points. References would dangle when the caller's scope - exits before the coroutine resumes. - - @par Purpose - - When iterating through large buffer sequences, it is often - more efficient to process buffers in batches rather than - one at a time. This class maintains a window of up to a - fixed, implementation-defined number of buffer descriptors - (currently 16). It refills the window from the underlying - sequence as buffers are consumed. - - @par Example - - Create a `buffer_param` from any buffer sequence and use - `data()` to get the current window of buffers. After - processing some bytes, call `consume()` to advance through - the sequence. - - @par !example example_2 - - - @par Virtual Interface Pattern - - This class enables passing arbitrary buffer sequences through - a virtual function boundary. The template function captures - the buffer sequence by value and drives the iteration, while - the virtual function receives a simple span. Plain CTAD - (`buffer_param bp(buffers)`) deduces `BS`'s own buffer type, so a - mutable sequence yields `span`. That does not match - `write_impl`'s `span` parameter. Use @ref const_buffer_param - to force `const_buffer` storage regardless of what `BS` is: - - @par !example example_3 - - - @tparam BS The buffer sequence type. Must satisfy - @ref ConstBufferSequence. - - @see @ref ConstBufferSequence, @ref MutableBufferSequence -*/ -template - requires ConstBufferSequence -class buffer_param -{ -public: - /// Names `const_buffer` when `MakeConst`, else `BS`'s own buffer type. - using buffer_type = std::conditional_t< - MakeConst, - const_buffer, - capy::buffer_type>; - -private: - decltype(begin(std::declval())) it_; - decltype(end(std::declval())) end_; - union { - int dummy_; - buffer_type arr_[detail::max_iovec_]; - }; - std::size_t size_ = 0; - std::size_t pos_ = 0; - - void - refill() - { - pos_ = 0; - size_ = 0; - for(; it_ != end_ && size_ < detail::max_iovec_; ++it_) - { - buffer_type buf(*it_); - if(buf.size() > 0) - ::new(&arr_[size_++]) buffer_type(buf); - } - } - -public: - /** Construct from a buffer sequence. - - @param bs The buffer sequence to wrap. The caller must - ensure the buffer sequence remains valid for the - lifetime of this object. - */ - explicit - buffer_param(BS const& bs) - : it_(begin(bs)) - , end_(end(bs)) - , dummy_(0) - { - refill(); - } - - /** Return the current window of buffer descriptors. - - Returns a span of buffer descriptors representing the - currently available portion of the buffer sequence. - The span contains at most a fixed, implementation-defined - number of buffers (currently 16). - - When the current window is exhausted, this function - automatically refills from the underlying sequence. - - @return A span of buffer descriptors. Empty span - indicates no more data is available. - */ - std::span - data() - { - if(pos_ >= size_) - refill(); - if(size_ == 0) - return {}; - return {arr_ + pos_, size_ - pos_}; - } - - /** Check if more buffers exist beyond the current window. - - Returns `true` if the underlying buffer sequence has - additional buffers that have not yet been loaded into - the current window. Call after @ref data to determine - whether the current window is the last one. - - @return `true` if more buffers remain in the sequence. - */ - bool - more() const noexcept - { - return it_ != end_; - } - - /** Consume bytes from the buffer sequence. - - Advances the current position by `n` bytes, consuming - data from the front of the sequence. Partially consumed - buffers are adjusted in place. - - @param n Number of bytes to consume. - */ - void - consume(std::size_t n) - { - while(n > 0 && pos_ < size_) - { - auto avail = arr_[pos_].size(); - if(n < avail) - { - arr_[pos_] += n; - n = 0; - } - else - { - n -= avail; - ++pos_; - } - } - } -}; - -/** Deduce the sequence type from the constructor argument. - - @tparam BS The buffer sequence type. -*/ -template -buffer_param(BS const&) -> buffer_param; - -/// Forces `buffer_param` to store windows as `const_buffer`, regardless of `BS`. -template -using const_buffer_param = buffer_param; - -} // namespace capy -} // namespace boost - -#endif diff --git a/include/boost/capy/buffers/buffer_slice.hpp b/include/boost/capy/buffers/buffer_slice.hpp deleted file mode 100644 index 35bc75de6..000000000 --- a/include/boost/capy/buffers/buffer_slice.hpp +++ /dev/null @@ -1,111 +0,0 @@ -// -// Copyright (c) 2026 Michael Vandeberg -// Copyright (c) 2026 Steve Gerbino -// -// 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 -#include -#include - -#include -#include -#include -#include - -namespace boost { -namespace capy { - -/** Names whichever buffer type `buffer_slice` returns for a sequence `BS`. - - A single buffer is closed under sub-ranging, so slicing it yields a - buffer of the same kind. Any other sequence yields the generic - `detail::slice_of` borrowed view. In both cases the result is itself - a buffer sequence — `slice_type ∈ { buffer, slice_of }`. -*/ -template -using slice_type = std::conditional_t< - ConstBuffer, - buffer_type, - detail::slice_of>; - -/** Return a byte sub-range of a buffer sequence, as a value. - - The result is itself a buffer sequence (`slice_type`), so pass it - directly to any operation expecting a buffer sequence. There is no - `.data()` and no separate concept to bind. For a single buffer the - result is an adjusted buffer; for any other sequence it is a borrowed - `slice_of` view. - - @par Lifetime - Except for the single-buffer case, the result borrows `seq`: it stores - iterators into the sequence, not a copy. `seq` must outlive the result. - The rvalue overload is deleted so a temporary cannot be sliced into a - dangling view. - - @par Complexity - Single forward pass to the cut points; never sums the whole sequence. - - @param seq The sequence to slice. Must outlive the result. - @param offset Bytes skipped from the front. Clamped to the total size. - @param length Bytes exposed, starting at `offset`. Defaults to the end. - - @return A `slice_type` value modeling the same buffer-sequence - concept as `seq` (mutable if `seq` is mutable). - - @par Example - @par !example example - - - @see @ref slice_type, @ref consuming_buffers -*/ -template - requires ConstBufferSequence -slice_type -buffer_slice( - BufferSequence const& seq, - std::size_t offset = 0, - std::size_t length = - (std::numeric_limits::max)()) noexcept -{ - if constexpr (ConstBuffer) - { - // A single buffer is its own slice: advance and (maybe) truncate. - buffer_type b = seq; - b += offset; // operator+= clamps to size() - if (length < b.size()) - b = buffer_type(b.data(), length); - return b; - } - else - { - return detail::slice_of(seq, offset, length); - } -} - -/** Rejects a temporary sequence at compile time, since the result would dangle. - - Slicing a temporary would yield an immediately dangling view (the - result borrows the sequence). Hoist the sequence into a named variable - first. -*/ -template - requires ConstBufferSequence -slice_type -buffer_slice( - BufferSequence const&& seq, - std::size_t offset = 0, - std::size_t length = - (std::numeric_limits::max)()) = delete; - -} // namespace capy -} // namespace boost - -#endif diff --git a/include/boost/capy/buffers/front.hpp b/include/boost/capy/buffers/buffers_front.hpp similarity index 77% rename from include/boost/capy/buffers/front.hpp rename to include/boost/capy/buffers/buffers_front.hpp index 798306769..e14047b91 100644 --- a/include/boost/capy/buffers/front.hpp +++ b/include/boost/capy/buffers/buffers_front.hpp @@ -8,8 +8,8 @@ // Official repository: https://github.com/cppalliance/capy // -#ifndef BOOST_CAPY_BUFFERS_FRONT_HPP -#define BOOST_CAPY_BUFFERS_FRONT_HPP +#ifndef BOOST_CAPY_BUFFERS_BUFFERS_FRONT_HPP +#define BOOST_CAPY_BUFFERS_BUFFERS_FRONT_HPP #include #include @@ -17,8 +17,9 @@ namespace boost { namespace capy { -// MrDocs 0.8.0 flags this object as `front: variable is undocumented`, as it -// does the other five buffer CPOs (begin, end, buffer_size, buffer_empty, +// MrDocs 0.8.0 flags this object as `buffers_front: variable is +// undocumented`, as it does the other five buffer CPOs +// (buffer_sequence_begin, buffer_sequence_end, buffer_size, buffers_empty, // buffer_copy). The finding is grandfathered in doc/lint/baseline.json. // Silencing it buys nothing: on the synthesised overload page MrDocs renders // neither the record-level docstring below nor one attached to the variable, @@ -42,8 +43,8 @@ constexpr struct operator()( MutableBufferSequence const& bs) const noexcept { - auto const it = begin(bs); - if(it != end(bs)) + auto const it = buffer_sequence_begin(bs); + if(it != buffer_sequence_end(bs)) return *it; return {}; } @@ -60,12 +61,12 @@ constexpr struct operator()( ConstBufferSequence const& bs) const noexcept { - auto const it = begin(bs); - if(it != end(bs)) + auto const it = buffer_sequence_begin(bs); + if(it != buffer_sequence_end(bs)) return *it; return {}; } -} const front{}; +} const buffers_front{}; } // capy } // boost diff --git a/include/boost/capy/buffers/buffers_iterator.hpp b/include/boost/capy/buffers/buffers_iterator.hpp new file mode 100644 index 000000000..cdcbf4035 --- /dev/null +++ b/include/boost/capy/buffers/buffers_iterator.hpp @@ -0,0 +1,431 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// 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_BUFFERS_ITERATOR_HPP +#define BOOST_CAPY_BUFFERS_BUFFERS_ITERATOR_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { + +namespace detail { + +// Walking back from the end needs an end iterator, not a sentinel +template +concept common_buffer_sequence = + ConstBufferSequence && + (ConstBuffer || std::ranges::common_range); + +} // detail + +/** A random access iterator over the bytes in a buffer sequence. + + A buffer sequence stores its bytes in several separate regions. + This iterator presents them as one run of bytes, so standard + algorithms such as `std::search` can scan across the boundaries + between buffers without first copying the data into contiguous + storage. Empty buffers in the sequence are skipped. + + The iterator dereferences to `ByteType&` for a + @ref MutableBufferSequence and to `ByteType const&` otherwise. + + Moving by `n` bytes walks the underlying buffers, so `operator+=`, + `operator-=`, `operator+`, `operator-` with an integer, and + `operator[]` are linear in the number of buffers crossed. Comparison + and the difference of two iterators are constant time. + + @par Preconditions + The buffer sequence and the memory it refers to outlive every + iterator obtained from it. Iterators from different sequences are + not comparable. + + @par Example + @code + std::array bufs{ + capy::make_buffer(std::string_view("GET / HTTP/1.1\r")), + capy::make_buffer(std::string_view("\n\r\nbody")) }; + + // Find the end of the header, which spans both buffers + char const crlfcrlf[] = "\r\n\r\n"; + auto it = std::search( + capy::buffers_begin(bufs), capy::buffers_end(bufs), + crlfcrlf, crlfcrlf + 4); + std::size_t header_size = it - capy::buffers_begin(bufs) + 4; + @endcode + + @tparam BufferSequence The buffer sequence type. It must be a single + buffer, or a range of buffers whose begin and end have the same type. + + @tparam ByteType The byte type presented by the iterator. Its size + must be 1. + + @see @ref buffers_begin, @ref buffers_end, + @ref buffer_sequence_begin +*/ +template + requires detail::common_buffer_sequence +class buffers_iterator +{ + static_assert(sizeof(ByteType) == 1, "ByteType must be a byte"); + + using sequence_iterator = decltype( + buffer_sequence_begin(std::declval())); + using buffer_type = capy::buffer_type; + using byte_type = std::conditional_t< + MutableBufferSequence, + ByteType, + ByteType const>; + + buffer_type current_buffer_{}; + std::size_t current_buffer_position_ = 0; + sequence_iterator current_{}; + sequence_iterator end_{}; + std::size_t position_ = 0; + + // Move to the next non-empty buffer, or to the end of the sequence + void + skip_forward() noexcept + { + while(current_ != end_) + { + current_buffer_ = buffer_type(*current_); + if(current_buffer_.size() > 0) + return; + ++current_; + } + current_buffer_ = {}; + } + + // Move to the previous non-empty buffer, positioned past its last byte + void + skip_backward() noexcept + { + do + { + --current_; + current_buffer_ = buffer_type(*current_); + } + while(current_buffer_.size() == 0); + current_buffer_position_ = current_buffer_.size(); + } + + void + advance(std::ptrdiff_t n) noexcept + { + if(n >= 0) + { + auto left = static_cast(n); + while(left > 0) + { + std::size_t const avail = + current_buffer_.size() - current_buffer_position_; + if(left < avail) + { + current_buffer_position_ += left; + position_ += left; + return; + } + left -= avail; + position_ += avail; + ++current_; + current_buffer_position_ = 0; + skip_forward(); + } + } + else + { + auto left = static_cast(-n); + while(left > 0) + { + if(left <= current_buffer_position_) + { + current_buffer_position_ -= left; + position_ -= left; + return; + } + left -= current_buffer_position_; + position_ -= current_buffer_position_; + skip_backward(); + } + } + } + +public: + /// The signed type of the distance between two iterators. + using difference_type = std::ptrdiff_t; + + /// The type of the bytes the iterator visits. + using value_type = ByteType; + + /// The type returned by `operator->`. + using pointer = byte_type*; + + /// The type returned by `operator*`. + using reference = byte_type&; + + /// The iterator category. + using iterator_category = std::random_access_iterator_tag; + + /// Construct a singular iterator. + buffers_iterator() = default; + + /** Return an iterator to the first byte of a buffer sequence. + + @param bs The buffer sequence. It must outlive the iterator. + + @return An iterator to the first byte, or the end iterator if + the sequence holds no bytes. + */ + static + buffers_iterator + begin(BufferSequence const& bs) noexcept + { + buffers_iterator it; + it.current_ = buffer_sequence_begin(bs); + it.end_ = buffer_sequence_end(bs); + it.skip_forward(); + return it; + } + + /** Return an iterator past the last byte of a buffer sequence. + + @param bs The buffer sequence. It must outlive the iterator. + + @return The end iterator. + */ + static + buffers_iterator + end(BufferSequence const& bs) noexcept + { + buffers_iterator it; + it.current_ = buffer_sequence_end(bs); + it.end_ = it.current_; + it.position_ = buffer_size(bs); + return it; + } + + /** Return a reference to the current byte. + + @par Preconditions + The iterator is not the end iterator. + */ + reference + operator*() const noexcept + { + return static_cast( + current_buffer_.data())[current_buffer_position_]; + } + + /** Return a pointer to the current byte. + + @par Preconditions + The iterator is not the end iterator. + */ + pointer + operator->() const noexcept + { + return &**this; + } + + /** Return a reference to the byte `n` positions away. + + @par Complexity + Linear in the number of buffers crossed. + + @param n The offset from this iterator, in bytes. + */ + reference + operator[](difference_type n) const noexcept + { + return *(*this + n); + } + + /// Advance to the next byte. + buffers_iterator& + operator++() noexcept + { + ++position_; + if(++current_buffer_position_ < current_buffer_.size()) + return *this; + ++current_; + current_buffer_position_ = 0; + skip_forward(); + return *this; + } + + /// Advance to the next byte, returning the previous position. + buffers_iterator + operator++(int) noexcept + { + auto tmp = *this; + ++*this; + return tmp; + } + + /// Move back to the previous byte. + buffers_iterator& + operator--() noexcept + { + advance(-1); + return *this; + } + + /// Move back to the previous byte, returning the previous position. + buffers_iterator + operator--(int) noexcept + { + auto tmp = *this; + --*this; + return tmp; + } + + /** Move by `n` bytes. + + @par Complexity + Linear in the number of buffers crossed. + + @param n The number of bytes to move; negative moves backward. + + @return A reference to `*this`. + */ + buffers_iterator& + operator+=(difference_type n) noexcept + { + advance(n); + return *this; + } + + /** Move back by `n` bytes. + + @par Complexity + Linear in the number of buffers crossed. + + @param n The number of bytes to move back. + + @return A reference to `*this`. + */ + buffers_iterator& + operator-=(difference_type n) noexcept + { + advance(-n); + return *this; + } + + /// Return an iterator `n` bytes after `it`. + friend + buffers_iterator + operator+(buffers_iterator it, difference_type n) noexcept + { + it += n; + return it; + } + + /// Return an iterator `n` bytes after `it`. + friend + buffers_iterator + operator+(difference_type n, buffers_iterator it) noexcept + { + it += n; + return it; + } + + /// Return an iterator `n` bytes before `it`. + friend + buffers_iterator + operator-(buffers_iterator it, difference_type n) noexcept + { + it -= n; + return it; + } + + /// Return the number of bytes from `b` to `a`. + friend + difference_type + operator-( + buffers_iterator const& a, + buffers_iterator const& b) noexcept + { + return static_cast(a.position_) - + static_cast(b.position_); + } + + /// Test whether two iterators refer to the same byte. + friend + bool + operator==( + buffers_iterator const& a, + buffers_iterator const& b) noexcept + { + return a.position_ == b.position_; + } + + /// Order two iterators by byte position. + friend + std::strong_ordering + operator<=>( + buffers_iterator const& a, + buffers_iterator const& b) noexcept + { + return a.position_ <=> b.position_; + } +}; + +/** Return an iterator to the first byte of a buffer sequence. + + @param bs The buffer sequence. It must outlive the iterator. + + @return `buffers_iterator::begin(bs)`. + + @see @ref buffers_iterator, @ref buffers_end +*/ +template +buffers_iterator +buffers_begin(BufferSequence const& bs) noexcept +{ + return buffers_iterator::begin(bs); +} + +/// Rejects a temporary sequence, since the iterator would dangle. +template +buffers_iterator +buffers_begin(BufferSequence const&& bs) = delete; + +/** Return an iterator past the last byte of a buffer sequence. + + @param bs The buffer sequence. It must outlive the iterator. + + @return `buffers_iterator::end(bs)`. + + @see @ref buffers_iterator, @ref buffers_begin +*/ +template +buffers_iterator +buffers_end(BufferSequence const& bs) noexcept +{ + return buffers_iterator::end(bs); +} + +/// Rejects a temporary sequence, since the iterator would dangle. +template +buffers_iterator +buffers_end(BufferSequence const&& bs) = delete; + +} // capy +} // boost + +#endif diff --git a/include/boost/capy/concept/read_stream.hpp b/include/boost/capy/concept/read_stream.hpp index d3fa8d3f3..231adaaf8 100644 --- a/include/boost/capy/concept/read_stream.hpp +++ b/include/boost/capy/concept/read_stream.hpp @@ -55,7 +55,7 @@ namespace capy { This lets generic composition algorithms such as `when_all` and `when_any` distinguish a completed transfer from a failure. - If `buffer_empty( buffers )` is `true`, `n` is 0. The empty + If `buffers_empty( buffers )` is `true`, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. diff --git a/include/boost/capy/concept/write_stream.hpp b/include/boost/capy/concept/write_stream.hpp index c29296c53..45abbcceb 100644 --- a/include/boost/capy/concept/write_stream.hpp +++ b/include/boost/capy/concept/write_stream.hpp @@ -59,7 +59,7 @@ namespace capy { composition algorithms such as `when_all` and `when_any` distinguish a completed transfer from a failure. - If `buffer_empty( buffers )` is `true`, `n` is 0. The empty + If `buffers_empty( buffers )` is `true`, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream. diff --git a/include/boost/capy/detail/buffer_array.hpp b/include/boost/capy/detail/buffer_array.hpp index 2d0155e14..4691042ff 100644 --- a/include/boost/capy/detail/buffer_array.hpp +++ b/include/boost/capy/detail/buffer_array.hpp @@ -109,8 +109,8 @@ class buffer_array buffer_array(BS const& bs) noexcept : dummy_(0) { - auto it = capy::begin(bs); - auto const last = capy::end(bs); + auto it = capy::buffer_sequence_begin(bs); + auto const last = capy::buffer_sequence_end(bs); while(it != last && n_ < N) { value_type b(*it); @@ -138,8 +138,8 @@ class buffer_array buffer_array(std::in_place_t, BS const& bs) : dummy_(0) { - auto it = capy::begin(bs); - auto const last = capy::end(bs); + auto it = capy::buffer_sequence_begin(bs); + auto const last = capy::buffer_sequence_end(bs); while(it != last) { value_type b(*it); diff --git a/include/boost/capy/buffers/consuming_buffers.hpp b/include/boost/capy/detail/consuming_buffers.hpp similarity index 88% rename from include/boost/capy/buffers/consuming_buffers.hpp rename to include/boost/capy/detail/consuming_buffers.hpp index 4384c7f54..311e5238c 100644 --- a/include/boost/capy/buffers/consuming_buffers.hpp +++ b/include/boost/capy/detail/consuming_buffers.hpp @@ -8,8 +8,8 @@ // Official repository: https://github.com/cppalliance/capy // -#ifndef BOOST_CAPY_BUFFERS_CONSUMING_BUFFERS_HPP -#define BOOST_CAPY_BUFFERS_CONSUMING_BUFFERS_HPP +#ifndef BOOST_CAPY_DETAIL_CONSUMING_BUFFERS_HPP +#define BOOST_CAPY_DETAIL_CONSUMING_BUFFERS_HPP #include #include @@ -20,6 +20,7 @@ namespace boost { namespace capy { +namespace detail { /** A cursor that drives consumption of a buffer sequence. @@ -33,12 +34,6 @@ namespace capy { **borrows** the underlying sequence (iterators + a consumed-byte offset). The sequence must outlive the cursor. That is the natural case when the cursor is a local of a composed operation that took its buffers by value. - - @par Example - @par !example example - - - @see @ref buffer_slice */ template requires ConstBufferSequence @@ -50,7 +45,7 @@ class consuming_buffers private: using iterator_type = - decltype(capy::begin(std::declval())); + decltype(capy::buffer_sequence_begin(std::declval())); iterator_type first_{}; iterator_type last_{}; @@ -62,8 +57,8 @@ class consuming_buffers @param s The sequence to consume. Must outlive the cursor. */ explicit consuming_buffers(Seq const& s) noexcept - : first_(capy::begin(s)) - , last_(capy::end(s)) + : first_(capy::buffer_sequence_begin(s)) + , last_(capy::buffer_sequence_end(s)) { } @@ -77,10 +72,10 @@ class consuming_buffers @return The bytes not yet consumed, as a buffer sequence. */ - detail::slice_of + slice_of data() const noexcept { - return detail::slice_of(first_, last_, front_skip_, 0); + return slice_of(first_, last_, front_skip_, 0); } /** Discard `n` bytes from the front, in place. @@ -115,6 +110,7 @@ class consuming_buffers template consuming_buffers(Seq const&) -> consuming_buffers; +} // namespace detail } // namespace capy } // namespace boost diff --git a/include/boost/capy/detail/slice_of.hpp b/include/boost/capy/detail/slice_of.hpp index a221fd300..043cc88da 100644 --- a/include/boost/capy/detail/slice_of.hpp +++ b/include/boost/capy/detail/slice_of.hpp @@ -24,9 +24,8 @@ namespace detail { /** A borrowed view over a byte sub-range of a buffer sequence. - `slice_of` is the generic result of `buffer_slice` for a sequence - that is not closed under sub-ranging (everything except a single - buffer). It models the same buffer-sequence concept as `BS`: + `slice_of` is the view `consuming_buffers::data()` returns. It + models the same buffer-sequence concept as `BS`: `MutableBufferSequence` if `BS` is mutable, otherwise `ConstBufferSequence`. It can therefore be passed anywhere a buffer sequence is expected. @@ -51,7 +50,7 @@ class slice_of /// The underlying sequence's iterator type. using iterator_type = - decltype(capy::begin(std::declval())); + decltype(capy::buffer_sequence_begin(std::declval())); private: iterator_type first_{}; @@ -147,8 +146,8 @@ class slice_of std::size_t length = (std::numeric_limits::max)()) noexcept { - first_ = capy::begin(bs); - last_ = capy::end(bs); + first_ = capy::buffer_sequence_begin(bs); + last_ = capy::buffer_sequence_end(bs); // Position first_/front_skip_ at byte `offset` (single forward pass). std::size_t skip = offset; @@ -167,7 +166,7 @@ class slice_of if (first_ == last_) return; // offset at or past the end: empty slice if (length == (std::numeric_limits::max)()) - return; // to-end: last_ already end(bs), back_skip_ == 0 + return; // to-end: last_ already at the end, back_skip_ == 0 // Walk `length` live bytes forward to fix last_/back_skip_. std::size_t left = length; diff --git a/include/boost/capy/io/write_now.hpp b/include/boost/capy/io/write_now.hpp index 03bc0fb60..b5bc3f568 100644 --- a/include/boost/capy/io/write_now.hpp +++ b/include/boost/capy/io/write_now.hpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -362,7 +362,7 @@ class write_now { std::size_t const total_size = buffer_size(buffers); std::size_t total_written = 0; - consuming_buffers cb(buffers); + detail::consuming_buffers cb(buffers); while(total_written < total_size) { auto r = @@ -387,7 +387,7 @@ class write_now // GCC ICE in expand_expr_real_1 (expr.cc:11376) // when the buffer slice spans a co_yield, so // the GCC path uses a separate simple coroutine. - consuming_buffers cb(buffers); + detail::consuming_buffers cb(buffers); while(total_written < total_size) { auto inner = stream_.write_some(cb.data()); diff --git a/include/boost/capy/read.hpp b/include/boost/capy/read.hpp index f7daee9a9..47c0a0186 100644 --- a/include/boost/capy/read.hpp +++ b/include/boost/capy/read.hpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include @@ -86,7 +86,7 @@ auto read(S& stream, MB buffers) -> task> { - consuming_buffers consuming(buffers); + detail::consuming_buffers consuming(buffers); std::size_t const total_size = buffer_size(buffers); std::size_t total_read = 0; diff --git a/include/boost/capy/read_at_least.hpp b/include/boost/capy/read_at_least.hpp index 000bdc4b2..89f5e8551 100644 --- a/include/boost/capy/read_at_least.hpp +++ b/include/boost/capy/read_at_least.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -106,7 +106,7 @@ auto read_at_least(S& stream, MB buffers, std::size_t n) -> task> { - consuming_buffers consuming(buffers); + detail::consuming_buffers consuming(buffers); std::size_t const total_size = buffer_size(buffers); if(n > total_size) diff --git a/include/boost/capy/test/buffer_to_string.hpp b/include/boost/capy/test/buffer_to_string.hpp index 1c95039ea..88c829832 100644 --- a/include/boost/capy/test/buffer_to_string.hpp +++ b/include/boost/capy/test/buffer_to_string.hpp @@ -41,8 +41,8 @@ buffer_to_string(Buffers const&... bufs) std::string result; result.reserve((buffer_size(bufs) + ...)); auto append = [&](auto const& bs) { - auto const e = end(bs); - for(auto it = begin(bs); it != e; ++it) + auto const e = buffer_sequence_end(bs); + for(auto it = buffer_sequence_begin(bs); it != e; ++it) { const_buffer b(*it); result.append( diff --git a/include/boost/capy/test/bufgrind.hpp b/include/boost/capy/test/bufgrind.hpp index 0e111288b..859bd706f 100644 --- a/include/boost/capy/test/bufgrind.hpp +++ b/include/boost/capy/test/bufgrind.hpp @@ -13,12 +13,13 @@ #include #include -#include +#include #include #include #include #include +#include #include #include @@ -36,10 +37,10 @@ namespace test { The split type automatically preserves mutability: passing a `MutableBufferSequence` yields halves that model @ref MutableBufferSequence, while passing a `ConstBufferSequence` - yields halves that model @ref ConstBufferSequence. Each half is - the buffer-sequence view exposed by a @ref buffer_slice over the - corresponding byte range, and can be passed directly to - `read_some`, `write_some`, `buffer_size`, etc. + yields halves that model @ref ConstBufferSequence. A single buffer + splits into two `mutable_buffer` or `const_buffer` values; any other + sequence splits into two borrowed views. Either way each half can be + passed directly to `read_some`, `write_some`, `buffer_size`, etc. @par Thread Safety Not thread-safe. @@ -55,8 +56,6 @@ namespace test { @par Step Size Example @par !example example_3 - - @see @ref buffer_slice */ template class bufgrind @@ -67,13 +66,39 @@ class bufgrind std::size_t pos_ = 0; public: - /// Names the buffer-sequence type `buffer_slice` yields for each half. - using slice_type = std::decay_t< - decltype(buffer_slice(std::declval()))>; + /// Names the buffer-sequence type of each half. + using slice_type = std::conditional_t< + ConstBuffer, + buffer_type, + detail::slice_of>; /// Pairs the two `slice_type` halves that @ref next yields together. using split_type = std::pair; +private: + static slice_type + slice( + BS const& bs, + std::size_t offset, + std::size_t length = + (std::numeric_limits::max)()) noexcept + { + if constexpr (ConstBuffer) + { + slice_type b = bs; + b += offset; + if(length < b.size()) + b = slice_type(b.data(), length); + return b; + } + else + { + return slice_type(bs, offset, length); + } + } + +public: + /** Construct a buffer grinder. @param bs The buffer sequence to iterate over. @@ -131,8 +156,8 @@ class bufgrind await_resume() { split_type result{ - buffer_slice(self_->bs_, 0, self_->pos_), - buffer_slice(self_->bs_, self_->pos_) + slice(self_->bs_, 0, self_->pos_), + slice(self_->bs_, self_->pos_) }; if(self_->pos_ < self_->size_) self_->pos_ = (std::min)(self_->pos_ + self_->step_, self_->size_); diff --git a/include/boost/capy/test/read_stream.hpp b/include/boost/capy/test/read_stream.hpp index e0a09e01b..9c6c28ef4 100644 --- a/include/boost/capy/test/read_stream.hpp +++ b/include/boost/capy/test/read_stream.hpp @@ -167,7 +167,7 @@ class read_stream { // Empty buffer is a no-op regardless of // stream state, stop token, or fuse. - if(buffer_empty(buffers_)) + if(buffers_empty(buffers_)) return {std::error_code(), 0}; if(canceled_) diff --git a/include/boost/capy/test/stream.hpp b/include/boost/capy/test/stream.hpp index 6291b94ac..f8d2dc01f 100644 --- a/include/boost/capy/test/stream.hpp +++ b/include/boost/capy/test/stream.hpp @@ -328,7 +328,7 @@ class stream bool await_ready() const noexcept { - if(buffer_empty(buffers_)) + if(buffers_empty(buffers_)) return true; auto* st = self_->state_.get(); auto& side = st->sides[self_->index_]; @@ -374,7 +374,7 @@ class stream stop_cb_active_ = false; } - if(buffer_empty(buffers_)) + if(buffers_empty(buffers_)) return {std::error_code(), 0}; if(canceled_) diff --git a/include/boost/capy/test/write_stream.hpp b/include/boost/capy/test/write_stream.hpp index 32c8f84a2..bc6998fb0 100644 --- a/include/boost/capy/test/write_stream.hpp +++ b/include/boost/capy/test/write_stream.hpp @@ -187,7 +187,7 @@ class write_stream [[nodiscard]] results await_resume() { - if(buffer_empty(buffers_)) + if(buffers_empty(buffers_)) return {std::error_code(), 0}; if(canceled_) diff --git a/include/boost/capy/write.hpp b/include/boost/capy/write.hpp index 802c91d2f..5d73a2f48 100644 --- a/include/boost/capy/write.hpp +++ b/include/boost/capy/write.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -91,7 +91,7 @@ namespace capy { template auto write(S& stream, CB buffers) -> task> { - consuming_buffers consuming(buffers); + detail::consuming_buffers consuming(buffers); std::size_t const total_size = buffer_size(buffers); std::size_t total_written = 0; diff --git a/include/boost/capy/write_at_least.hpp b/include/boost/capy/write_at_least.hpp index 806854be4..0655c8682 100644 --- a/include/boost/capy/write_at_least.hpp +++ b/include/boost/capy/write_at_least.hpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include @@ -102,7 +102,7 @@ template auto write_at_least(S& stream, CB buffers, std::size_t n) -> task> { - consuming_buffers consuming(buffers); + detail::consuming_buffers consuming(buffers); std::size_t const total_size = buffer_size(buffers); if(n > total_size) diff --git a/test/doc/reference/README.md b/test/doc/reference/README.md index 259f71767..f6af2ff98 100644 --- a/test/doc/reference/README.md +++ b/test/doc/reference/README.md @@ -66,7 +66,7 @@ Overload sets and duplicated records can share a `.` name. Give each region a declaration naming the symbols it belongs to: ```cpp -// mrdocs::for buffer_param-0e buffer_param-0c +// mrdocs::for ``` The names are MrDocs anchors, which you can read from the generated reference. diff --git a/test/doc/reference/buffer_param.record.cpp b/test/doc/reference/buffer_param.record.cpp deleted file mode 100644 index c2667ad0b..000000000 --- a/test/doc/reference/buffer_param.record.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// -// 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 -// - -// Reference examples for boost::capy::buffer_param, injected into its documentation by -// doc/addons/extensions/reference-snippets.lua. Declared in: -// include/boost/capy/buffers/buffer_param.hpp -// -// The tagged regions are what the reference renders; the includes, -// suppressions and namespaces around them are scaffolding. Each region gets -// its own namespace so that examples which reuse a name still compile. - -#include "../doc_warnings.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -namespace capy = boost::capy; - -namespace { -namespace ex_1 { -// tag::example_1[] -capy::task<> write(capy::ConstBufferSequence auto buffers); // CORRECT -capy::task<> write(capy::ConstBufferSequence auto& buffers); // WRONG - dangling reference -// end::example_1[] -} // namespace ex_1 -namespace ex_2 { -// tag::example_2[] -capy::task<> send(capy::ConstBufferSequence auto buffers) -{ - capy::buffer_param bp(buffers); - while(true) - { - auto bufs = bp.data(); - if(bufs.empty()) - break; - auto n = co_await do_something(bufs); - bp.consume(n); - } -} -// end::example_2[] -} // namespace ex_2 -namespace ex_3 { -// tag::example_3[] -class base -{ -public: - template - capy::task<> write(BS buffers) - { - capy::const_buffer_param bp(buffers); - while(true) - { - auto bufs = bp.data(); - if(bufs.empty()) - break; - std::size_t n = 0; - co_await write_impl(bufs, n); - bp.consume(n); - } - } - -protected: - virtual capy::task<> write_impl( - std::span buffers, - std::size_t& bytes_written) = 0; -}; -// end::example_3[] -} // namespace ex_3 - -} // namespace diff --git a/test/doc/reference/buffer_slice.function.cpp b/test/doc/reference/buffer_slice.function.cpp deleted file mode 100644 index 96fb3d6f1..000000000 --- a/test/doc/reference/buffer_slice.function.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// 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 -// - -// Reference examples for boost::capy::buffer_slice, injected into its documentation by -// doc/addons/extensions/reference-snippets.lua. Declared in: -// include/boost/capy/buffers/buffer_slice.hpp -// -// The tagged regions are what the reference renders; the includes, -// suppressions and namespaces around them are scaffolding. Each region gets -// its own namespace so that examples which reuse a name still compile. - -#include "../doc_warnings.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -namespace capy = boost::capy; - -namespace { - -namespace ex_1 { -// tag::example[] -capy::task<> send_in_two_parts( capy::any_write_stream& sock, capy::const_buffer bufs ) -{ - co_await capy::write(sock, capy::buffer_slice(bufs, 0, 16384)); // first 16 KB - auto rest = capy::buffer_slice(bufs, 16384); // drop first 16 KB - co_await capy::write(sock, rest); -} -// end::example[] -} // namespace ex_1 - -} // namespace diff --git a/test/doc/reference/consuming_buffers.record.cpp b/test/doc/reference/consuming_buffers.record.cpp deleted file mode 100644 index 3165318f5..000000000 --- a/test/doc/reference/consuming_buffers.record.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// -// 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 -// - -// Reference examples for boost::capy::consuming_buffers, injected into its documentation by -// doc/addons/extensions/reference-snippets.lua. Declared in: -// include/boost/capy/buffers/consuming_buffers.hpp -// -// The tagged regions are what the reference renders; the includes, -// suppressions and namespaces around them are scaffolding. Each region gets -// its own namespace so that examples which reuse a name still compile. - -#include "../doc_warnings.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -namespace capy = boost::capy; - -namespace { -namespace ex_1 { -// tag::example[] -template -capy::task> read_until_full( capy::any_read_stream& stream, Buffers buffers ) -{ - capy::consuming_buffers consuming(buffers); - std::size_t total = 0, want = capy::buffer_size(buffers); - while (total < want) - { - auto [ec, n] = co_await stream.read_some(consuming.data()); - consuming.consume(n); - total += n; - if (ec && total < want) co_return {ec, total}; - } - co_return {std::error_code(), total}; -} -// end::example[] -} // namespace ex_1 - -} // namespace diff --git a/test/doc/snippets/5b_composite_buffers.cpp b/test/doc/snippets/5b_composite_buffers.cpp index 3dccbbad7..95bf74e28 100644 --- a/test/doc/snippets/5b_composite_buffers.cpp +++ b/test/doc/snippets/5b_composite_buffers.cpp @@ -20,6 +20,9 @@ // tag::make_buffer_include[] #include // end::make_buffer_include[] +// tag::buffers_iterator_include[] +#include +// end::buffers_iterator_include[] #include #include #include @@ -30,6 +33,7 @@ #include #include +#include #include #include #include @@ -150,7 +154,7 @@ std::vector send_lengths; template void send(Buffers const& bufs) { - send_lengths.push_back(capy::buffer_length(bufs)); + send_lengths.push_back(capy::buffers_count(bufs)); } // The custom type used by the heterogeneous-composition fragment. @@ -169,7 +173,8 @@ struct chained_buffers template void process(Buffers const& bufs) { - for (auto it = capy::begin(bufs); it != capy::end(bufs); ++it) + for (auto it = capy::buffer_sequence_begin(bufs); + it != capy::buffer_sequence_end(bufs); ++it) { capy::const_buffer buf = *it; // Process buf.data(), buf.size() @@ -236,13 +241,13 @@ struct composite_buffers_test { // tag::begin_end_uniform[] capy::const_buffer single; - auto it = capy::begin(single); // Returns pointer to `single` - auto e = capy::end(single); // Returns pointer past `single` + auto it = capy::buffer_sequence_begin(single); // pointer to `single` + auto e = capy::buffer_sequence_end(single); // pointer past `single` assert(std::distance(it, e) == 1); std::array multi; - auto it2 = capy::begin(multi); // Returns multi.begin() - auto e2 = capy::end(multi); // Returns multi.end() + auto it2 = capy::buffer_sequence_begin(multi); // Returns multi.begin() + auto e2 = capy::buffer_sequence_end(multi); // Returns multi.end() assert(std::distance(it2, e2) == 3); // end::begin_end_uniform[] BOOST_TEST(it == &single); @@ -259,10 +264,11 @@ struct composite_buffers_test std::size_t len = // tag::length_equivalent[] - std::distance(capy::begin(buf), capy::end(buf)) + std::distance( + capy::buffer_sequence_begin(buf), capy::buffer_sequence_end(buf)) // end::length_equivalent[] ; - BOOST_TEST(capy::buffer_length(buf) == len); + BOOST_TEST(capy::buffers_count(buf) == len); }; test_case(capy::const_buffer{}); @@ -297,6 +303,24 @@ struct composite_buffers_test capy::const_buffer(data, 2), capy::const_buffer(data + 2, 2)}); } + void + testIterateBytes() + { + // tag::iterate_bytes[] + std::array bufs{ + capy::make_buffer(std::string_view("GET / HTTP/1.1\r")), + capy::make_buffer(std::string_view("\n\r\nbody")) }; + + // The terminator straddles the two buffers + std::string_view const crlfcrlf = "\r\n\r\n"; + auto it = std::search( + capy::buffers_begin(bufs), capy::buffers_end(bufs), + crlfcrlf.begin(), crlfcrlf.end()); + std::size_t header_size = (it - capy::buffers_begin(bufs)) + 4; + // end::iterate_bytes[] + BOOST_TEST(header_size == 18); + } + void run() { @@ -306,6 +330,7 @@ struct composite_buffers_test testLength(); testHeterogeneous(); testIterate(); + testIterateBytes(); } }; diff --git a/test/doc/snippets/5c_system_io_integration.cpp b/test/doc/snippets/5c_system_io_integration.cpp index 1e800a394..147b11f09 100644 --- a/test/doc/snippets/5c_system_io_integration.cpp +++ b/test/doc/snippets/5c_system_io_integration.cpp @@ -20,12 +20,6 @@ // tag::make_buffer_include[] #include // end::make_buffer_include[] -// tag::buffer_slice_include[] -#include -// end::buffer_slice_include[] -// tag::consuming_buffers_include[] -#include -// end::consuming_buffers_include[] #include #include diff --git a/test/doc/snippets/5d_buffer_algo.cpp b/test/doc/snippets/5d_buffer_algo.cpp index 28ca788eb..eba790756 100644 --- a/test/doc/snippets/5d_buffer_algo.cpp +++ b/test/doc/snippets/5d_buffer_algo.cpp @@ -15,23 +15,8 @@ #include "../doc_warnings.hpp" #include -#include - -// tag::buffer_slice_include[] -#include -// end::buffer_slice_include[] -// tag::consuming_buffers_include[] -#include -// end::consuming_buffers_include[] - #include -#include -#include -#include -#include -#include -#include -#include +#include #include #include @@ -66,44 +51,6 @@ using namespace std::string_view_literals; // Buffer algorithms // --------------------------------------------------------------------- -using Stream = capy::test::stream; - -// tag::read_all[] -template -capy::task read_all(Stream& stream, Buffers buffers) -{ - capy::consuming_buffers consuming(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(consuming.data()); - consuming.consume(n); - total += n; - if (ec) - break; - } - - co_return total; -} -// end::read_all[] - -capy::task<> send_sliced( - Stream& stream, - std::array const& bufs) -{ - // tag::buffer_slice[] - // send only the first 16 KB - co_await capy::write(stream, capy::buffer_slice(bufs, 0, 16384)); - // everything after the first 16 KB - auto rest = capy::buffer_slice(bufs, 16384); - co_await capy::write(stream, rest); - // end::buffer_slice[] - BOOST_TEST(capy::buffer_size(rest) == capy::buffer_size(bufs) - 16384); -} - - // Bidirectional iterator presenting each chunk as a const_buffer. Kept // out of the page fragment, which focuses on the sequence shape. class chunk_iterator @@ -191,9 +138,9 @@ struct buffer_algo_test { // tag::buffer_empty_example[] auto buf = capy::make_buffer(""sv); // 0 bytes - assert(capy::buffer_empty(buf)); + assert(capy::buffers_empty(buf)); // end::buffer_empty_example[] - BOOST_TEST(capy::buffer_empty(buf)); + BOOST_TEST(capy::buffers_empty(buf)); } // tag::bigger_buffer_copy_example[] @@ -272,40 +219,6 @@ struct buffer_algo_test BOOST_TEST(std::memcmp(small_data, "gh", 2) == 0); } - void - testReadAll() - { - auto [a, b] = capy::test::make_stream_pair(); - b.provide("abcdefghijklmnopqrst"); // 20 bytes readable from a - a.set_max_read_size(7); // force several partial reads - - std::vector head(8), tail(12); - std::array bufs{ - capy::make_buffer(head), capy::make_buffer(tail)}; - - std::size_t got = 0; - capy::test::run_blocking([&](std::size_t n) { got = n; })( - read_all(a, bufs)); - - BOOST_TEST(got == 20); - BOOST_TEST(std::string(head.begin(), head.end()) == "abcdefgh"); - BOOST_TEST(std::string(tail.begin(), tail.end()) == "ijklmnopqrst"); - } - - void - testBufferSlice() - { - auto [a, b] = capy::test::make_stream_pair(); - std::string part1(10000, 'x'); - std::string part2(10000, 'y'); - std::array bufs{ - capy::make_buffer(part1), capy::make_buffer(part2)}; - - capy::test::run_blocking()(send_sliced(a, bufs)); - - BOOST_TEST(b.data() == part1 + part2); - } - void run() { @@ -314,8 +227,6 @@ struct buffer_algo_test testBufferCopy(); testPartialCopy(); testCrossSequenceCopy(); - testReadAll(); - testBufferSlice(); } }; diff --git a/test/doc/snippets/9o_why_not_cobalt_concepts.cpp b/test/doc/snippets/9o_why_not_cobalt_concepts.cpp index 8e82c285b..e298ef633 100644 --- a/test/doc/snippets/9o_why_not_cobalt_concepts.cpp +++ b/test/doc/snippets/9o_why_not_cobalt_concepts.cpp @@ -76,7 +76,7 @@ struct child_operation // composition algorithms such as when_all and when_any distinguish // a completed transfer from a failure. // -// If buffer_empty( buffers ) is true, n is 0. The empty +// If buffers_empty( buffers ) is true, n is 0. The empty // buffer is not itself a cause for error, but ec may reflect // the state of the stream. // diff --git a/test/unit/buffers/buffer.cpp b/test/unit/buffers/buffer.cpp index 851708ed6..87f64a955 100644 --- a/test/unit/buffers/buffer.cpp +++ b/test/unit/buffers/buffer.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -621,7 +622,7 @@ struct buffer_test BOOST_TEST_EQ(buffer_copy(arr, const_buffer(src, 8)), 8); BOOST_TEST_EQ(std::string_view(data, 8), "abcdefgh"); - consuming_buffers cb(arr); + detail::consuming_buffers cb(arr); cb.consume(5); BOOST_TEST_EQ(buffer_size(cb.data()), 3); @@ -630,7 +631,9 @@ struct buffer_test one.whole = const_buffer(src, 8); one.push_back(mutable_buffer(data, 2)); BOOST_TEST_EQ(buffer_size(one), 8); - BOOST_TEST_EQ(buffer_size(buffer_slice(one, 3)), 5); + detail::consuming_buffers one_cb(one); + one_cb.consume(3); + BOOST_TEST_EQ(buffer_size(one_cb.data()), 5); // Algorithms follow the concepts, however the compiler // classifies the type @@ -642,7 +645,9 @@ struct buffer_test range.push_back(const_buffer(src + 3, 2)); constexpr bool single = ConstBuffer; BOOST_TEST_EQ(buffer_size(range), single ? 8 : 5); - BOOST_TEST_EQ(buffer_size(buffer_slice(range, 1)), single ? 7 : 4); + detail::consuming_buffers range_cb(range); + range_cb.consume(1); + BOOST_TEST_EQ(buffer_size(range_cb.data()), single ? 7 : 4); char out[8] = {}; BOOST_TEST_EQ( buffer_copy(mutable_buffer(out, 8), range), single ? 8 : 5); @@ -654,35 +659,35 @@ struct buffer_test char data[9] = "12345678"; // empty const_buffer - BOOST_TEST(buffer_empty(const_buffer())); - BOOST_TEST(buffer_empty(const_buffer(data, 0))); + BOOST_TEST(buffers_empty(const_buffer())); + BOOST_TEST(buffers_empty(const_buffer(data, 0))); // non-empty const_buffer - BOOST_TEST(! buffer_empty(const_buffer(data, 1))); - BOOST_TEST(! buffer_empty(const_buffer(data, 5))); + BOOST_TEST(! buffers_empty(const_buffer(data, 1))); + BOOST_TEST(! buffers_empty(const_buffer(data, 5))); // empty mutable_buffer - BOOST_TEST(buffer_empty(mutable_buffer())); - BOOST_TEST(buffer_empty(mutable_buffer(data, 0))); + BOOST_TEST(buffers_empty(mutable_buffer())); + BOOST_TEST(buffers_empty(mutable_buffer(data, 0))); // non-empty mutable_buffer - BOOST_TEST(! buffer_empty(mutable_buffer(data, 1))); - BOOST_TEST(! buffer_empty(mutable_buffer(data, 5))); + BOOST_TEST(! buffers_empty(mutable_buffer(data, 1))); + BOOST_TEST(! buffers_empty(mutable_buffer(data, 5))); // empty buffer_pair (both empty) { std::array cbp{{ {data, 0}, {data, 0} }}; - BOOST_TEST(buffer_empty(cbp)); + BOOST_TEST(buffers_empty(cbp)); } // non-empty buffer_pair (one non-empty) { std::array cbp{{ {data, 0}, {data, 3} }}; - BOOST_TEST(! buffer_empty(cbp)); + BOOST_TEST(! buffers_empty(cbp)); } { std::array cbp{{ {data, 3}, {data, 0} }}; - BOOST_TEST(! buffer_empty(cbp)); + BOOST_TEST(! buffers_empty(cbp)); } // buffer sequence: all empty @@ -693,7 +698,7 @@ struct buffer_test { data, 0 } }; std::span s(cb, 3); - BOOST_TEST(buffer_empty(s)); + BOOST_TEST(buffers_empty(s)); } // buffer sequence: some empty, one non-empty @@ -704,7 +709,7 @@ struct buffer_test { data, 0 } }; std::span s(cb, 3); - BOOST_TEST(! buffer_empty(s)); + BOOST_TEST(! buffers_empty(s)); } // buffer sequence: none empty @@ -715,13 +720,13 @@ struct buffer_test { data, 3 } }; std::span s(cb, 3); - BOOST_TEST(! buffer_empty(s)); + BOOST_TEST(! buffers_empty(s)); } // empty span (zero elements) { std::span s; - BOOST_TEST(buffer_empty(s)); + BOOST_TEST(buffers_empty(s)); } } diff --git a/test/unit/buffers/buffer_param.cpp b/test/unit/buffers/buffer_param.cpp deleted file mode 100644 index 24f57eecb..000000000 --- a/test/unit/buffers/buffer_param.cpp +++ /dev/null @@ -1,401 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) -// -// 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 -// - -// Test that header file is self-contained. -#include - -#include "test_buffers.hpp" - -#include -#include -#include -#include - -namespace boost { -namespace capy { - -namespace { - -// Helper to compute total bytes in a span -std::size_t -total_size(std::span bufs) -{ - std::size_t n = 0; - for(auto const& b : bufs) - n += b.size(); - return n; -} - -std::size_t -total_size(std::span bufs) -{ - std::size_t n = 0; - for(auto const& b : bufs) - n += b.size(); - return n; -} - -// Helper to copy data from buffer span to string -std::string -to_string(std::span bufs) -{ - std::string result; - for(auto const& b : bufs) - result.append( - static_cast(b.data()), - b.size()); - return result; -} - -// Helper to consume all data from a buffer_param -template -std::string -consume_all(buffer_param& bp) -{ - std::string result; - while(true) - { - auto bufs = bp.data(); - if(bufs.empty()) - break; - result += to_string(bufs); - bp.consume(total_size(bufs)); - } - return result; -} - -} // namespace - -struct buffer_param_test -{ - void - testConstSingleBuffer() - { - // Single const_buffer - { - std::string data = "Hello, World!"; - const_buffer buf(data.data(), data.size()); - - buffer_param bp(buf); - auto result = consume_all(bp); - BOOST_TEST_EQ(result, data); - } - - // Empty buffer - { - const_buffer buf; - buffer_param bp(buf); - auto result = consume_all(bp); - BOOST_TEST(result.empty()); - } - } - - void - testConstMultipleBuffers() - { - // Vector of buffers - { - std::string s1 = "Hello, "; - std::string s2 = "World"; - std::string s3 = "!"; - - std::vector bufs = { - const_buffer(s1.data(), s1.size()), - const_buffer(s2.data(), s2.size()), - const_buffer(s3.data(), s3.size()) - }; - - buffer_param bp(bufs); - auto result = consume_all(bp); - BOOST_TEST_EQ(result, "Hello, World!"); - } - - // With empty buffers interspersed (should be skipped) - { - std::string s1 = "AB"; - std::string s2 = "CD"; - - std::vector bufs = { - const_buffer(), - const_buffer(s1.data(), s1.size()), - const_buffer(), - const_buffer(s2.data(), s2.size()), - const_buffer() - }; - - buffer_param bp(bufs); - auto result = consume_all(bp); - BOOST_TEST_EQ(result, "ABCD"); - } - } - - void - testConstPartialConsume() - { - std::string data = "0123456789"; - const_buffer buf(data.data(), data.size()); - - // Consume 3 bytes at a time - { - buffer_param bp(buf); - - auto bufs = bp.data(); - BOOST_TEST_EQ(total_size(bufs), 10u); - - // Consume 3 - bp.consume(3); - bufs = bp.data(); - BOOST_TEST_EQ(total_size(bufs), 7u); - BOOST_TEST_EQ( - static_cast(bufs[0].data())[0], - '3'); - - // Consume 4 more - bp.consume(4); - bufs = bp.data(); - BOOST_TEST_EQ(total_size(bufs), 3u); - BOOST_TEST_EQ( - static_cast(bufs[0].data())[0], - '7'); - - // Consume remaining - bp.consume(3); - bufs = bp.data(); - BOOST_TEST(bufs.empty()); - } - } - - void - testConstLargeSequence() - { - // More than 16 buffers (tests window refill) - std::vector strings; - std::vector bufs; - std::string expected; - - for(int i = 0; i < 50; ++i) - { - strings.push_back(std::to_string(i) + ","); - expected += strings.back(); - } - - for(auto const& s : strings) - bufs.emplace_back(s.data(), s.size()); - - buffer_param bp(bufs); - auto result = consume_all(bp); - BOOST_TEST_EQ(result, expected); - } - - void - testMutableSingleBuffer() - { - // Mutable buffer - write into it - { - char data[32] = {}; - mutable_buffer buf(data, sizeof(data)); - - buffer_param bp(buf); - auto bufs = bp.data(); - - BOOST_TEST_EQ(bufs.size(), 1u); - BOOST_TEST_EQ(bufs[0].size(), sizeof(data)); - - // Write some data - std::memcpy(bufs[0].data(), "Test", 4); - bp.consume(4); - - bufs = bp.data(); - BOOST_TEST_EQ(bufs[0].size(), sizeof(data) - 4); - - // Write more - std::memcpy(bufs[0].data(), "Data", 4); - bp.consume(4); - - BOOST_TEST_EQ(std::string(data, 8), "TestData"); - } - } - - void - testMutableMultipleBuffers() - { - char buf1[8] = {}; - char buf2[8] = {}; - char buf3[8] = {}; - - std::vector bufs = { - mutable_buffer(buf1, sizeof(buf1)), - mutable_buffer(buf2, sizeof(buf2)), - mutable_buffer(buf3, sizeof(buf3)) - }; - - buffer_param bp(bufs); - - // Fill all buffers through the param - std::size_t total = 0; - int chunk = 0; - while(true) - { - auto spans = bp.data(); - if(spans.empty()) - break; - - for(auto& b : spans) - { - std::memset(b.data(), 'A' + chunk, b.size()); - total += b.size(); - } - bp.consume(total_size(spans)); - ++chunk; - } - - BOOST_TEST_EQ(total, 24u); - - // Verify all buffers were written - BOOST_TEST_EQ(std::string(buf1, 8), "AAAAAAAA"); - BOOST_TEST_EQ(std::string(buf2, 8), "AAAAAAAA"); - BOOST_TEST_EQ(std::string(buf3, 8), "AAAAAAAA"); - } - - void - testBufferType() - { - // Verify buffer_type is correct for const sequences - { - std::vector bufs; - buffer_param bp(bufs); - static_assert(std::is_same_v< - decltype(bp)::buffer_type, const_buffer>); - } - - // Verify buffer_type is correct for mutable sequences - { - std::vector bufs; - buffer_param bp(bufs); - static_assert(std::is_same_v< - decltype(bp)::buffer_type, mutable_buffer>); - } - } - - void - testMore() - { - // Single buffer — no more after first window - { - std::string data = "Hello"; - const_buffer buf(data.data(), data.size()); - buffer_param bp(buf); - - auto bufs = bp.data(); - BOOST_TEST(! bufs.empty()); - BOOST_TEST(! bp.more()); - } - - // Empty sequence — no more - { - std::vector bufs; - buffer_param bp(bufs); - auto d = bp.data(); - BOOST_TEST(d.empty()); - BOOST_TEST(! bp.more()); - } - - // Exactly max_iovec_ buffers — fits in one window - { - std::vector strings(detail::max_iovec_, "x"); - std::vector bufs; - for(auto const& s : strings) - bufs.emplace_back(s.data(), s.size()); - - buffer_param bp(bufs); - auto d = bp.data(); - BOOST_TEST(! d.empty()); - BOOST_TEST(! bp.more()); - } - - // One more than max_iovec_ — needs two windows - { - std::vector strings( - detail::max_iovec_ + 1, "x"); - std::vector bufs; - for(auto const& s : strings) - bufs.emplace_back(s.data(), s.size()); - - buffer_param bp(bufs); - auto d = bp.data(); - BOOST_TEST(! d.empty()); - BOOST_TEST(bp.more()); - - bp.consume(total_size(d)); - d = bp.data(); - BOOST_TEST(! d.empty()); - BOOST_TEST(! bp.more()); - - bp.consume(total_size(d)); - d = bp.data(); - BOOST_TEST(d.empty()); - } - } - - void - testPartialByteConsumption() - { - std::string data = "ABCDEFGHIJ"; - const_buffer buf(data.data(), data.size()); - - buffer_param bp(buf); - std::string result; - std::size_t n = 5; - - while(n > 0) - { - auto bufs = bp.data(); - if(bufs.empty()) - break; - auto chunk = (std::min)(n, total_size(bufs)); - // Copy only 'chunk' bytes - std::size_t copied = 0; - for(auto const& b : bufs) - { - auto take = (std::min)(b.size(), chunk - copied); - result.append( - static_cast(b.data()), - take); - copied += take; - if(copied >= chunk) - break; - } - bp.consume(chunk); - n -= chunk; - } - - BOOST_TEST_EQ(result, "ABCDE"); - } - - void - run() - { - testConstSingleBuffer(); - testConstMultipleBuffers(); - testConstPartialConsume(); - testConstLargeSequence(); - testMutableSingleBuffer(); - testMutableMultipleBuffers(); - testBufferType(); - testMore(); - testPartialByteConsumption(); - } -}; - -TEST_SUITE( - buffer_param_test, - "boost.capy.buffers.buffer_param"); - -} // capy -} // boost diff --git a/test/unit/buffers/buffer_slice.cpp b/test/unit/buffers/buffer_slice.cpp deleted file mode 100644 index 42a83fa38..000000000 --- a/test/unit/buffers/buffer_slice.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// 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 -// - -// Test that the header is self-contained. -#include - -#include - -#include -#include -#include -#include - -#include "test_suite.hpp" - -namespace boost { -namespace capy { - -namespace { - -// Flatten the bytes of a buffer sequence for byte-exact comparison. -template -std::string -flatten(Seq const& s) -{ - std::string out; - for (auto it = capy::begin(s); it != capy::end(s); ++it) - { - const_buffer b(*it); - out.append(static_cast(b.data()), b.size()); - } - return out; -} - -struct buffer_slice_test -{ - void - testReturnsBufferSequence() - { - char a[10], b[20]; - std::memset(a, 'A', sizeof(a)); - std::memset(b, 'B', sizeof(b)); - std::array bufs = { - mutable_buffer(a, sizeof(a)), mutable_buffer(b, sizeof(b)) }; - - auto s = buffer_slice(bufs, 0); - // The result IS a buffer sequence (not a Slice with .data()). - static_assert(MutableBufferSequence, - "buffer_slice's result must model MutableBufferSequence"); - static_assert(std::same_as>, - "buffer_slice must return slice_type"); - BOOST_TEST_EQ(buffer_size(s), 30u); - BOOST_TEST_EQ(flatten(s), std::string(10, 'A') + std::string(20, 'B')); - } - - void - testConstInput() - { - char a[10] = {}; - std::array cb = { const_buffer(a, sizeof(a)) }; - auto s = buffer_slice(cb); - static_assert(ConstBufferSequence, - "buffer_slice over const input must model ConstBufferSequence"); - static_assert(!MutableBufferSequence, - "buffer_slice over const input must NOT model MutableBufferSequence"); - } - - void - testOffsetLength() - { - char a[10], b[20]; - std::memset(a, 'A', sizeof(a)); - std::memset(b, 'B', sizeof(b)); - std::array bufs = { - mutable_buffer(a, sizeof(a)), mutable_buffer(b, sizeof(b)) }; - - auto s = buffer_slice(bufs, 5, 10); - BOOST_TEST_EQ(buffer_size(s), 10u); - BOOST_TEST_EQ(flatten(s), std::string(5, 'A') + std::string(5, 'B')); - } - - void - testOffsetToEnd() - { - char a[10], b[20]; - std::memset(a, 'A', sizeof(a)); - std::memset(b, 'B', sizeof(b)); - std::array bufs = { - mutable_buffer(a, sizeof(a)), mutable_buffer(b, sizeof(b)) }; - - auto s = buffer_slice(bufs, 12); - BOOST_TEST_EQ(buffer_size(s), 18u); - BOOST_TEST_EQ(flatten(s), std::string(18, 'B')); - } - - void - testSingleBufferSelfSlice() - { - char a[10]; - std::memset(a, 'A', sizeof(a)); - - // A single buffer is closed under sub-ranging: slicing yields a - // buffer of the same kind, NOT a slice_of wrapper. - mutable_buffer mb(a, sizeof(a)); - auto s = buffer_slice(mb, 2, 5); - static_assert(std::same_as, - "slicing a mutable_buffer yields a mutable_buffer"); - BOOST_TEST_EQ(buffer_size(s), 5u); - BOOST_TEST_EQ(flatten(s), std::string(5, 'A')); - - const_buffer cbuf(a, sizeof(a)); - auto cs = buffer_slice(cbuf, 3); - static_assert(std::same_as, - "slicing a const_buffer yields a const_buffer"); - BOOST_TEST_EQ(buffer_size(cs), 7u); - } - - void - testClampAndEmpty() - { - char a[10]; - std::memset(a, 'A', sizeof(a)); - std::array bufs = { mutable_buffer(a, sizeof(a)) }; - - auto over = buffer_slice(bufs, 4, 999); // length clamps to remaining - BOOST_TEST_EQ(buffer_size(over), 6u); - - auto past = buffer_slice(bufs, 100); // offset past end -> empty - BOOST_TEST_EQ(buffer_size(past), 0u); - } - - void - run() - { - testReturnsBufferSequence(); - testConstInput(); - testOffsetLength(); - testOffsetToEnd(); - testSingleBufferSelfSlice(); - testClampAndEmpty(); - } -}; - -TEST_SUITE(buffer_slice_test, "boost.capy.buffers.buffer_slice"); - -} // (anon) - -} // namespace capy -} // namespace boost diff --git a/test/unit/buffers/buffers_front.cpp b/test/unit/buffers/buffers_front.cpp new file mode 100644 index 000000000..fb7db72a4 --- /dev/null +++ b/test/unit/buffers/buffers_front.cpp @@ -0,0 +1,143 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// 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 +// + +// Test that the header is self-contained. +#include + +#include +#include +#include +#include + +#include "test_suite.hpp" + +namespace boost { +namespace capy { + +namespace { + +struct user_mutable +{ + char* p; + std::size_t n; + + operator mutable_buffer() const noexcept { return { p, n }; } + operator const_buffer() const noexcept { return { p, n }; } +}; + +struct user_const +{ + char const* p; + std::size_t n; + + operator const_buffer() const noexcept { return { p, n }; } +}; + +struct buffers_front_test +{ + void + testSingleBuffer() + { + char data[8] = {}; + + mutable_buffer mb(data, 8); + auto m = buffers_front(mb); + static_assert(std::same_as); + BOOST_TEST_EQ(m.data(), data); + BOOST_TEST_EQ(m.size(), 8); + + const_buffer cb(data, 3); + auto c = buffers_front(cb); + static_assert(std::same_as); + BOOST_TEST_EQ(c.data(), data); + BOOST_TEST_EQ(c.size(), 3); + } + + void + testRange() + { + char a[4] = {}; + char b[6] = {}; + + std::array mbs{{ + mutable_buffer(a, 4), mutable_buffer(b, 6) }}; + auto m = buffers_front(mbs); + static_assert(std::same_as); + BOOST_TEST_EQ(m.data(), a); + BOOST_TEST_EQ(m.size(), 4); + + std::array cbs{{ + const_buffer(b, 6), const_buffer(a, 4) }}; + auto c = buffers_front(cbs); + static_assert(std::same_as); + BOOST_TEST_EQ(c.data(), b); + BOOST_TEST_EQ(c.size(), 6); + } + + void + testEmptyRange() + { + std::vector v; + auto m = buffers_front(v); + BOOST_TEST_EQ(m.data(), nullptr); + BOOST_TEST_EQ(m.size(), 0); + + std::vector cv; + auto c = buffers_front(cv); + BOOST_TEST_EQ(c.data(), nullptr); + BOOST_TEST_EQ(c.size(), 0); + } + + void + testUserTypes() + { + char a[4] = {}; + char b[6] = {}; + + user_mutable um{ a, 4 }; + auto m = buffers_front(um); + static_assert(std::same_as); + BOOST_TEST_EQ(m.data(), a); + BOOST_TEST_EQ(m.size(), 4); + + std::array ums{{ { b, 6 }, { a, 4 } }}; + auto ma = buffers_front(ums); + static_assert(std::same_as); + BOOST_TEST_EQ(ma.data(), b); + BOOST_TEST_EQ(ma.size(), 6); + + user_const uc{ a, 4 }; + auto c = buffers_front(uc); + static_assert(std::same_as); + BOOST_TEST_EQ(c.data(), a); + BOOST_TEST_EQ(c.size(), 4); + + std::array ucs{{ { b, 6 }, { a, 4 } }}; + auto ca = buffers_front(ucs); + static_assert(std::same_as); + BOOST_TEST_EQ(ca.data(), b); + BOOST_TEST_EQ(ca.size(), 6); + } + + void + run() + { + testSingleBuffer(); + testRange(); + testEmptyRange(); + testUserTypes(); + } +}; + +TEST_SUITE(buffers_front_test, "boost.capy.buffers.buffers_front"); + +} // (anon) + +} // namespace capy +} // namespace boost diff --git a/test/unit/buffers/buffers_iterator.cpp b/test/unit/buffers/buffers_iterator.cpp new file mode 100644 index 000000000..0ce50bea2 --- /dev/null +++ b/test/unit/buffers/buffers_iterator.cpp @@ -0,0 +1,269 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// 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 +// + +// Test that the header is self-contained. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_suite.hpp" + +namespace boost { +namespace capy { + +namespace { + +using cseq = std::array; +using mseq = std::vector; + +static_assert(std::random_access_iterator>); +static_assert(std::random_access_iterator>); +static_assert(std::random_access_iterator>); +static_assert(std::random_access_iterator< + buffers_iterator>>); +static_assert(std::same_as< + std::iter_reference_t>, char const&>); +static_assert(std::same_as< + std::iter_reference_t>, char&>); +static_assert(std::same_as< + std::iter_reference_t>, + unsigned char&>); + +// The iterator would point into a destroyed temporary +template +constexpr bool begin_accepts_rvalue = + requires { buffers_begin(T{}); }; +template +constexpr bool end_accepts_rvalue = + requires { buffers_end(T{}); }; +static_assert(! begin_accepts_rvalue); +static_assert(! end_accepts_rvalue); + +// A range whose end is a sentinel is a buffer sequence, but the iterator +// needs the end to be an iterator it can walk back from +struct non_empty +{ + bool operator()(const_buffer b) const noexcept { return b.size() > 0; } +}; +using sentinel_seq = decltype(std::views::take_while( + std::declval&>(), non_empty{})); +static_assert(ConstBufferSequence); +static_assert(! std::ranges::common_range); + +template +constexpr bool has_byte_iterator = + requires(T const& t) { buffers_begin(t); buffers_end(t); }; +static_assert(! has_byte_iterator); +static_assert(has_byte_iterator); +static_assert(has_byte_iterator); + +struct buffers_iterator_test +{ + // "abc" | "" | "de" | "" | "fghij", with empty buffers to skip + static constexpr std::string_view text = "abcdefghij"; + + static + cseq + make_cseq() + { + char const* p = text.data(); + return {{ + const_buffer(p, 3), + const_buffer(p + 3, 0), + const_buffer(p + 3, 2), + const_buffer(p + 5, 0), + const_buffer(p + 5, 5) }}; + } + + void + testForward() + { + auto const bs = make_cseq(); + std::string out(buffers_begin(bs), buffers_end(bs)); + BOOST_TEST_EQ(out, text); + BOOST_TEST_EQ(buffers_end(bs) - buffers_begin(bs), + static_cast(text.size())); + } + + void + testBackward() + { + auto const bs = make_cseq(); + std::string out; + auto const first = buffers_begin(bs); + for(auto it = buffers_end(bs); it != first;) + out.push_back(*--it); + std::reverse(out.begin(), out.end()); + BOOST_TEST_EQ(out, text); + } + + void + testRandomAccess() + { + auto const bs = make_cseq(); + auto const first = buffers_begin(bs); + auto const n = static_cast(text.size()); + for(std::ptrdiff_t i = 0; i <= n; ++i) + { + auto a = first + i; + BOOST_TEST_EQ(a - first, i); + if(i < n) + { + BOOST_TEST_EQ(*a, text[i]); + BOOST_TEST_EQ(first[i], text[i]); + } + for(std::ptrdiff_t j = 0; j <= n; ++j) + { + auto b = a + (j - i); + BOOST_TEST_EQ(b - first, j); + BOOST_TEST_EQ(b - a, j - i); + BOOST_TEST_EQ(b < a, j < i); + BOOST_TEST_EQ(b == a, j == i); + b -= j - i; + BOOST_TEST(b == a); + } + } + BOOST_TEST(first + n == buffers_end(bs)); + BOOST_TEST(buffers_end(bs) - n == first); + } + + void + testSearchAcrossBuffers() + { + auto const bs = make_cseq(); + std::string_view const needle = "cdef"; + auto it = std::search( + buffers_begin(bs), buffers_end(bs), + needle.begin(), needle.end()); + BOOST_TEST_EQ(it - buffers_begin(bs), 2); + } + + void + testSingleBuffer() + { + const_buffer const b(text.data(), text.size()); + std::string out(buffers_begin(b), buffers_end(b)); + BOOST_TEST_EQ(out, text); + } + + void + testEmpty() + { + std::array const none{}; + BOOST_TEST(buffers_begin(none) == buffers_end(none)); + BOOST_TEST_EQ(buffers_end(none) - buffers_begin(none), 0); + + mseq const nothing; + BOOST_TEST(buffers_begin(nothing) == buffers_end(nothing)); + } + + void + testMutable() + { + char a[3] = {}; + char b[4] = {}; + mseq const bs{ + mutable_buffer(a, sizeof(a)), + mutable_buffer(b, 0), + mutable_buffer(b, sizeof(b)) }; + std::string_view const src = "1234567"; + std::copy(src.begin(), src.end(), buffers_begin(bs)); + BOOST_TEST_EQ(std::string_view(a, 3), "123"); + BOOST_TEST_EQ(std::string_view(b, 4), "4567"); + + buffers_begin(bs)[4] = 'x'; + BOOST_TEST_EQ(b[1], 'x'); + } + + // The sequence's own iterator is only bidirectional + void + testList() + { + auto const arr = make_cseq(); + std::list const bs(arr.begin(), arr.end()); + std::string out(buffers_begin(bs), buffers_end(bs)); + BOOST_TEST_EQ(out, text); + + std::string back; + auto const first = buffers_begin(bs); + for(auto it = buffers_end(bs); it != first;) + back.push_back(*--it); + std::reverse(back.begin(), back.end()); + BOOST_TEST_EQ(back, text); + + auto const n = static_cast(text.size()); + for(std::ptrdiff_t i = 0; i < n; ++i) + { + BOOST_TEST_EQ(first[i], text[i]); + BOOST_TEST_EQ((buffers_end(bs) - (n - i))[0], text[i]); + } + } + + void + testSingleMutable() + { + char data[4] = {}; + mutable_buffer const b(data, sizeof(data)); + auto it = buffers_begin(b); + static_assert(std::same_as); + *it = 'a'; + it[3] = 'd'; + BOOST_TEST_EQ(data[0], 'a'); + BOOST_TEST_EQ(data[3], 'd'); + BOOST_TEST_EQ(buffers_end(b) - it, 4); + } + + void + testValueInitialized() + { + buffers_iterator a{}; + buffers_iterator b{}; + BOOST_TEST(a == b); + BOOST_TEST_EQ(a - b, 0); + } + + void + testByteType() + { + auto const bs = make_cseq(); + auto it = buffers_iterator::begin(bs); + BOOST_TEST_EQ(it[3], static_cast('d')); + } + + void + run() + { + testForward(); + testBackward(); + testRandomAccess(); + testSearchAcrossBuffers(); + testSingleBuffer(); + testEmpty(); + testMutable(); + testList(); + testSingleMutable(); + testValueInitialized(); + testByteType(); + } +}; + +TEST_SUITE(buffers_iterator_test, "boost.capy.buffers.buffers_iterator"); + +} // (anon) + +} // namespace capy +} // namespace boost diff --git a/test/unit/buffers/io_param.cpp b/test/unit/buffers/io_param.cpp index 7ee758b11..2d65aba41 100644 --- a/test/unit/buffers/io_param.cpp +++ b/test/unit/buffers/io_param.cpp @@ -65,15 +65,15 @@ class consumer_param : public consumer< is_mutable_buffer_sequence_v> { BufferSequence bs_; - decltype(begin(bs_)) it_; - decltype(end(bs_)) end_; + decltype(buffer_sequence_begin(bs_)) it_; + decltype(buffer_sequence_end(bs_)) end_; public: template consumer_param(T&& t) : bs_(std::forward(t)) - , it_(begin(bs_)) - , end_(end(bs_)) + , it_(buffer_sequence_begin(bs_)) + , end_(buffer_sequence_end(bs_)) { } diff --git a/test/unit/buffers/slice_of.cpp b/test/unit/buffers/slice_of.cpp index 1f8d327f1..f708104d1 100644 --- a/test/unit/buffers/slice_of.cpp +++ b/test/unit/buffers/slice_of.cpp @@ -29,7 +29,8 @@ std::string flatten(Seq const& s) { std::string out; - for (auto it = capy::begin(s); it != capy::end(s); ++it) + for (auto it = capy::buffer_sequence_begin(s); + it != capy::buffer_sequence_end(s); ++it) { const_buffer b(*it); out.append(static_cast(b.data()), b.size()); diff --git a/test/unit/buffers/test_buffers.hpp b/test/unit/buffers/test_buffers.hpp index a30c18d97..e2d0aa8fd 100644 --- a/test/unit/buffers/test_buffers.hpp +++ b/test/unit/buffers/test_buffers.hpp @@ -11,7 +11,7 @@ #define BOOST_CAPY_BUFFERS_TEST_BUFFERS_HPP #include -#include +#include #include #include #include @@ -104,8 +104,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = begin(bs); - auto const end_ = end(bs); + auto it = buffer_sequence_begin(bs); + auto const end_ = buffer_sequence_end(bs); while(it != end_) { auto b = *it; @@ -121,8 +121,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = begin(bs); - auto const end_ = end(bs); + auto it = buffer_sequence_begin(bs); + auto const end_ = buffer_sequence_end(bs); while(it != end_) { auto b = *it; @@ -138,8 +138,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = begin(ct); - auto const end_ = end(ct); + auto it = buffer_sequence_begin(ct); + auto const end_ = buffer_sequence_end(ct); while(it != end_) { auto b = *it; @@ -155,8 +155,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = begin(ct); - auto const end_ = end(ct); + auto it = buffer_sequence_begin(ct); + auto const end_ = buffer_sequence_end(ct); while(it != end_) { auto b = *it; @@ -172,8 +172,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = end(bs); - auto const begin_ = begin(bs); + auto it = buffer_sequence_end(bs); + auto const begin_ = buffer_sequence_begin(bs); while(it != begin_) { --it; @@ -189,8 +189,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = end(bs); - auto const begin_ = begin(bs); + auto it = buffer_sequence_end(bs); + auto const begin_ = buffer_sequence_begin(bs); while(it != begin_) { it--; @@ -206,8 +206,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = end(ct); - auto const begin_ = begin(ct); + auto it = buffer_sequence_end(ct); + auto const begin_ = buffer_sequence_begin(ct); while(it != begin_) { --it; @@ -223,8 +223,8 @@ check_iterators( { s.clear(); s.reserve(pat.size() + 1); - auto it = end(ct); - auto const begin_ = begin(ct); + auto it = buffer_sequence_end(ct); + auto const begin_ = buffer_sequence_begin(ct); while(it != begin_) { it--; @@ -268,7 +268,7 @@ grind_front( { // sans_prefix: drop the first n bytes (value-returning) auto pat = trimmed_front(pat0, n); - auto bs = buffer_slice(bs0, n); + auto bs = detail::slice_of(bs0, n); check_eq(bs, pat); check_iterators(bs, pat, tmp); @@ -279,7 +279,8 @@ grind_front( for(std::size_t m = 0; m <= pat.size() + 1; ++m) { auto pat2 = trimmed_front(pat0, n + m); - auto bs2 = buffer_slice(bs0, n + m); + auto bs2 = + detail::slice_of(bs0, n + m); check_eq(bs2, pat2); } } @@ -288,7 +289,7 @@ grind_front( // prefix: keep only the first n bytes auto pat = kept_front(pat0, n); std::size_t const len = (n < total) ? n : total; - auto bs = buffer_slice(bs0, 0, len); + auto bs = detail::slice_of(bs0, 0, len); check_eq(bs, pat); check_iterators(bs, pat, tmp); } @@ -311,7 +312,7 @@ grind_back( // sans_suffix: drop the last n bytes (keep first total - n) auto pat = trimmed_back(pat0, n); std::size_t const len = (n < total) ? total - n : 0; - auto bs = buffer_slice(bs0, 0, len); + auto bs = detail::slice_of(bs0, 0, len); check_eq(bs, pat); check_iterators(bs, pat, tmp); if(deep) @@ -323,7 +324,8 @@ grind_back( auto pat2 = trimmed_back(pat0, n + m); std::size_t const new_len = (n + m < total) ? total - (n + m) : 0; - auto bs2 = buffer_slice(bs0, 0, new_len); + auto bs2 = detail::slice_of( + bs0, 0, new_len); check_eq(bs2, pat2); } } @@ -332,7 +334,7 @@ grind_back( // suffix: keep only the last n bytes auto pat = kept_back(pat0, n); std::size_t const offset = (n < total) ? total - n : 0; - auto bs = buffer_slice(bs0, offset); + auto bs = detail::slice_of(bs0, offset); check_eq(bs, pat); check_iterators(bs, pat, tmp); } diff --git a/test/unit/buffers/consuming_buffers.cpp b/test/unit/detail/consuming_buffers.cpp similarity index 92% rename from test/unit/buffers/consuming_buffers.cpp rename to test/unit/detail/consuming_buffers.cpp index a5bebf1a5..5842f7f70 100644 --- a/test/unit/buffers/consuming_buffers.cpp +++ b/test/unit/detail/consuming_buffers.cpp @@ -8,7 +8,7 @@ // // Test that the header is self-contained. -#include +#include #include @@ -20,6 +20,7 @@ namespace boost { namespace capy { +namespace detail { namespace { @@ -28,7 +29,8 @@ std::string flatten(Seq const& s) { std::string out; - for (auto it = capy::begin(s); it != capy::end(s); ++it) + for (auto it = capy::buffer_sequence_begin(s); + it != capy::buffer_sequence_end(s); ++it) { const_buffer b(*it); out.append(static_cast(b.data()), b.size()); @@ -127,7 +129,7 @@ struct consuming_buffers_test consuming_buffers consuming(bufs); consuming.consume(1000); // clamped to 10 BOOST_TEST_EQ(buffer_size(consuming.data()), 0u); - BOOST_TEST(buffer_empty(consuming.data())); + BOOST_TEST(buffers_empty(consuming.data())); } void @@ -142,9 +144,10 @@ struct consuming_buffers_test } }; -TEST_SUITE(consuming_buffers_test, "boost.capy.buffers.consuming_buffers"); +TEST_SUITE(consuming_buffers_test, "boost.capy.detail.consuming_buffers"); } // (anon) +} // namespace detail } // namespace capy } // namespace boost