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 88cb07da3..4109a978d 100644 --- a/doc/modules/ROOT/pages/8.examples/8c.buffer-composition.adoc +++ b/doc/modules/ROOT/pages/8.examples/8c.buffer-composition.adoc @@ -5,7 +5,7 @@ Composing buffer sequences without allocation for scatter/gather I/O. == What You Will Learn * Creating buffers from different sources -* Using `const_buffer_pair` and `mutable_buffer_pair` for scatter/gather I/O +* Using `std::array` and `std::array` for scatter/gather I/O * Zero-allocation buffer sequence patterns == Prerequisites @@ -44,15 +44,15 @@ void demonstrate_single_buffers() std::cout << "Vector buffer: " << vec_buf.size() << " bytes\n"; } -void demonstrate_buffer_pair() +void demonstrate_two_buffer_scatter() { - std::cout << "\n=== Buffer Pair (Scatter/Gather) ===\n\n"; - - // capy::const_buffer_pair is std::array + std::cout << "\n=== Two-Buffer Scatter/Gather ===\n\n"; + + // A 2-element buffer sequence is just a std::array std::string header = "Content-Type: text/plain\r\n\r\n"; std::string body = "Hello, World!"; - - capy::const_buffer_pair message = {{ + + std::array message = {{ capy::make_buffer(header), capy::make_buffer(body) }}; @@ -108,7 +108,7 @@ void demonstrate_mutable_buffers() char buf1[64]; char buf2[64]; - capy::mutable_buffer_pair recv_buffers = {{ + std::array recv_buffers = {{ capy::mutable_buffer(buf1, sizeof(buf1)), capy::mutable_buffer(buf2, sizeof(buf2)) }}; @@ -124,7 +124,7 @@ void demonstrate_mutable_buffers() int main() { demonstrate_single_buffers(); - demonstrate_buffer_pair(); + demonstrate_two_buffer_scatter(); demonstrate_buffer_array(); demonstrate_mutable_buffers(); @@ -152,17 +152,17 @@ auto arr_buf = capy::make_buffer(arr, sizeof(arr) - 1); // mutable_buffer `make_buffer` creates buffer views from various sources. No data is copied—the buffers reference the original storage. -=== Buffer Pairs +=== Two-Buffer Scatter/Gather [source,cpp] ---- -capy::const_buffer_pair message = {{ +std::array message = {{ capy::make_buffer(header), capy::make_buffer(body) }}; ---- -`const_buffer_pair` is `std::array` — a fixed-size buffer sequence for scatter/gather I/O. Similarly, `mutable_buffer_pair` holds two mutable buffers. +Capy's buffer-sequence concepts accept any range of `const_buffer` or `mutable_buffer`, so `std::array` is a buffer sequence with no further wrapping required. Use `mutable_buffer` for receive paths. === Multi-Buffer Arrays @@ -175,7 +175,7 @@ std::array http_response = {{ }}; ---- -For more than two buffers, use `std::array` directly. 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 `buffer_length()` for querying total bytes and buffer count. === Scatter/Gather I/O @@ -199,7 +199,7 @@ String buffer: 13 bytes Array buffer: 10 bytes Vector buffer: 6 bytes -=== Buffer Pair (Scatter/Gather) === +=== Two-Buffer Scatter/Gather === Total message size: 41 bytes Buffer count: 2 diff --git a/doc/modules/ROOT/pages/9.design/9m.WhyNotCobalt.adoc b/doc/modules/ROOT/pages/9.design/9m.WhyNotCobalt.adoc index 956817d0e..b28a2a6b5 100644 --- a/doc/modules/ROOT/pages/9.design/9m.WhyNotCobalt.adoc +++ b/doc/modules/ROOT/pages/9.design/9m.WhyNotCobalt.adoc @@ -433,14 +433,6 @@ Capy has one `DynamicBuffer` concept. The v1/v2 split in Asio exists because of | Yes | -| `buffer_pair` -| Yes -| - -| `slice` -| Yes -| - | `front` | Yes | @@ -449,10 +441,6 @@ Capy has one `DynamicBuffer` concept. The v1/v2 split in Asio exists because of | Yes | -| `buffer_array` -| Yes -| - | Byte-level trimming | Yes | diff --git a/doc/modules/ROOT/pages/why-capy.adoc b/doc/modules/ROOT/pages/why-capy.adoc index 17d6f84e2..e03bdbe8f 100644 --- a/doc/modules/ROOT/pages/why-capy.adoc +++ b/doc/modules/ROOT/pages/why-capy.adoc @@ -162,7 +162,7 @@ Asio got buffer sequences right. The concept-driven approach—`ConstBufferSeque Capy doesn't reinvent this. We adopt Asio's buffer sequence model because it works. -But we improve on it. Asio provides the basics; Capy extends them. Need to trim bytes from the front of a buffer sequence? Asio makes you work for it. Capy provides `buffer_slice` and `front`—byte-range slicing primitives for efficient byte-level manipulation. Need a circular buffer for protocol parsing? Capy has `circular_dynamic_buffer`. Need to compose two buffers without copying? `buffer_pair`. +But we improve on it. Asio provides the basics; Capy extends them. Need to trim bytes from the front of a buffer sequence? Asio makes you work for it. Capy provides `buffer_slice` and `front`—byte-range slicing primitives for efficient byte-level manipulation. Need a circular buffer for protocol parsing? Capy has `circular_dynamic_buffer`. Need to compose two buffers without copying? Use `std::array` (or any range of buffers) directly — Capy's buffer-sequence concepts accept arbitrary ranges. And then there's the `DynamicBuffer` mess. If you've used Asio, you've encountered the confusing split between `DynamicBuffer_v1` and `DynamicBuffer_v2`. This exists because of a fundamental problem: when an async operation takes a buffer by value and completes via callback, who owns the buffer? The original design had flaws. The "fix" created two incompatible versions. (See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1100r0.html[P1100R0] for the full story.) @@ -173,8 +173,8 @@ One more thing: `std::ranges` cannot help here. `ranges::size` returns the numbe === What Capy Offers * `ConstBufferSequence`, `MutableBufferSequence`, `DynamicBuffer` — core concepts (Asio-compatible) -* `flat_dynamic_buffer`, `circular_dynamic_buffer`, `buffer_pair` — additional concrete types -* `buffer_slice`, `front`, `buffer_array` — byte-level manipulation utilities +* `flat_dynamic_buffer`, `circular_dynamic_buffer` — additional concrete buffer types +* `buffer_slice`, `front` — byte-level manipulation utilities === Comparison @@ -227,11 +227,6 @@ One more thing: `std::ranges` cannot help here. `ranges::size` returns the numbe | | -| `buffer_pair` -| -| -| - | `buffer_slice` | | @@ -247,11 +242,6 @@ One more thing: `std::ranges` cannot help here. `ranges::size` returns the numbe | | -| `buffer_array` -| -| -| - | `buffer_copy` | `buffer_copy` | diff --git a/example/asio/api/capy_streams.cpp b/example/asio/api/capy_streams.cpp index ccbd14b8f..712cb7c95 100644 --- a/example/asio/api/capy_streams.cpp +++ b/example/asio/api/capy_streams.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -108,7 +107,7 @@ class asio_socket cancel_ = std::make_shared(env->stop_token); self_->socket_.async_read_some( - capy::to_asio(capy::mutable_buffer_array<8>(buffers_)), + capy::to_asio(buffers_), net::bind_cancellation_slot( cancel_->signal.slot(), [this, ex = env->executor]( @@ -172,7 +171,7 @@ class asio_socket cancel_ = std::make_shared(env->stop_token); self_->socket_.async_write_some( - capy::to_asio(capy::const_buffer_array<8>(buffers_)), + capy::to_asio(buffers_), net::bind_cancellation_slot( cancel_->signal.slot(), [this, ex = env->executor]( diff --git a/example/buffer-composition/buffer_composition.cpp b/example/buffer-composition/buffer_composition.cpp index ed1f17fc3..554a8ae03 100644 --- a/example/buffer-composition/buffer_composition.cpp +++ b/example/buffer-composition/buffer_composition.cpp @@ -34,15 +34,15 @@ void demonstrate_single_buffers() std::cout << "Vector buffer: " << vec_buf.size() << " bytes\n"; } -void demonstrate_buffer_pair() +void demonstrate_two_buffer_scatter() { - std::cout << "\n=== Buffer Pair (Scatter/Gather) ===\n\n"; + std::cout << "\n=== Two-Buffer Scatter/Gather ===\n\n"; - // capy::const_buffer_pair is std::array + // Two-element scatter/gather sequence (just a std::array) std::string header = "Content-Type: text/plain\r\n\r\n"; std::string body = "Hello, World!"; - - capy::const_buffer_pair message = {{ + + std::array message = {{ capy::make_buffer(header), capy::make_buffer(body) }}; @@ -98,7 +98,7 @@ void demonstrate_mutable_buffers() char buf1[64]; char buf2[64]; - capy::mutable_buffer_pair recv_buffers = {{ + std::array recv_buffers = {{ capy::mutable_buffer(buf1, sizeof(buf1)), capy::mutable_buffer(buf2, sizeof(buf2)) }}; @@ -114,7 +114,7 @@ void demonstrate_mutable_buffers() int main() { demonstrate_single_buffers(); - demonstrate_buffer_pair(); + demonstrate_two_buffer_scatter(); demonstrate_buffer_array(); demonstrate_mutable_buffers(); diff --git a/include/boost/capy.hpp b/include/boost/capy.hpp index 3f868dcdb..8af85a453 100644 --- a/include/boost/capy.hpp +++ b/include/boost/capy.hpp @@ -37,15 +37,12 @@ // Buffers #include #include -#include #include #include #include #include #include #include -#include -#include #include #include diff --git a/include/boost/capy/buffers.hpp b/include/boost/capy/buffers.hpp index f166548c4..9fe509754 100644 --- a/include/boost/capy/buffers.hpp +++ b/include/boost/capy/buffers.hpp @@ -32,26 +32,6 @@ namespace capy { class const_buffer; class mutable_buffer; -/// Tag type for customizing `buffer_size` via `tag_invoke`. -struct size_tag {}; - -/// Tag type for customizing slice operations via `tag_invoke`. -struct slice_tag {}; - -/** Constants for slice customization. - - Passed to `tag_invoke` overloads to specify which portion - of a buffer sequence to retain. -*/ -enum class slice_how -{ - /// Remove bytes from the front of the sequence. - remove_prefix, - - /// Keep only the first N bytes. - keep_prefix -}; - /** A reference to a contiguous region of writable memory. Represents a pointer and size pair for a modifiable byte range. @@ -111,35 +91,6 @@ class mutable_buffer n_ -= n; return *this; } - - /// Slice customization point for `tag_invoke`. - friend - void - tag_invoke( - slice_tag const&, - mutable_buffer& b, - slice_how how, - std::size_t n) noexcept - { - b.do_slice(how, n); - } - -private: - void do_slice( - slice_how how, std::size_t n) noexcept - { - switch(how) - { - case slice_how::remove_prefix: - *this += n; - return; - - case slice_how::keep_prefix: - if( n < n_) - n_ = n; - return; - } - } }; /** A reference to a contiguous region of read-only memory. @@ -208,35 +159,6 @@ class const_buffer n_ -= n; return *this; } - - /// Slice customization point for `tag_invoke`. - friend - void - tag_invoke( - slice_tag const&, - const_buffer& b, - slice_how how, - std::size_t n) noexcept - { - b.do_slice(how, n); - } - -private: - void do_slice( - slice_how how, std::size_t n) noexcept - { - switch(how) - { - case slice_how::remove_prefix: - *this += n; - return; - - case slice_how::keep_prefix: - if( n < n_) - n_ = n; - return; - } - } }; /** Concept for sequences of read-only buffer regions. @@ -332,19 +254,6 @@ constexpr struct end_mrdocs_workaround_t } } end {}; -template -std::size_t -tag_invoke( - size_tag const&, - CB const& bs) noexcept -{ - std::size_t n = 0; - auto const e = end(bs); - for(auto it = begin(bs); it != e; ++it) - n += const_buffer(*it).size(); - return n; -} - /** Return the total byte count across all buffers in a sequence. Sums the `size()` of each buffer in the sequence. This differs @@ -358,12 +267,29 @@ tag_invoke( */ constexpr struct buffer_size_mrdocs_workaround_t { + // GCC 13 falsely flags reads of arr_[i].n_ in detail::buffer_array + // when iterating here. The class uses union storage with placement + // new for slots 0..n_-1, so reads inside this bounded loop are + // well-defined, but the optimizer can't prove the loop bound and + // warns. The runtime cost of value-initializing all N slots is + // non-trivial for non-trivial value types, so we suppress instead. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif template constexpr std::size_t operator()( CB const& bs) const noexcept { - return tag_invoke(size_tag{}, bs); + std::size_t n = 0; + auto const e = capy::end(bs); + for(auto it = capy::begin(bs); it != e; ++it) + n += const_buffer(*it).size(); + return n; } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif } buffer_size {}; /** Check if a buffer sequence contains no data. @@ -373,6 +299,11 @@ constexpr struct buffer_size_mrdocs_workaround_t */ constexpr struct buffer_empty_mrdocs_workaround_t { + // See note on buffer_size above — same union-storage false positive. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif template constexpr bool operator()( CB const& bs) const noexcept @@ -387,6 +318,9 @@ constexpr struct buffer_empty_mrdocs_workaround_t } return true; } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif } buffer_empty {}; namespace detail { diff --git a/include/boost/capy/buffers/buffer_pair.hpp b/include/boost/capy/buffers/buffer_pair.hpp deleted file mode 100644 index 20cf4e6c0..000000000 --- a/include/boost/capy/buffers/buffer_pair.hpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2023 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 -// - -#ifndef BOOST_CAPY_BUFFERS_BUFFER_PAIR_HPP -#define BOOST_CAPY_BUFFERS_BUFFER_PAIR_HPP - -#include -#include -#include - -namespace boost { -namespace capy { - -/** A constant buffer pair -*/ -using const_buffer_pair = std::array; - -/// Slice customization point for const_buffer_pair. -BOOST_CAPY_DECL -void -tag_invoke( - slice_tag const&, - const_buffer_pair& bs, - slice_how how, - std::size_t n) noexcept; - -/** A mutable buffer pair -*/ -using mutable_buffer_pair = std::array; - -/// Slice customization point for mutable_buffer_pair. -BOOST_CAPY_DECL -void -tag_invoke( - slice_tag const&, - mutable_buffer_pair& bs, - slice_how how, - std::size_t n) noexcept; - -} // capy -} // boost - -#endif diff --git a/include/boost/capy/buffers/buffer_slice.hpp b/include/boost/capy/buffers/buffer_slice.hpp index f199c8ece..2d83bfd0a 100644 --- a/include/boost/capy/buffers/buffer_slice.hpp +++ b/include/boost/capy/buffers/buffer_slice.hpp @@ -24,8 +24,7 @@ namespace capy { Constructs a view over a contiguous byte range of `seq`. The slice exposes its current bytes via `data()` (a buffer sequence) - and supports incremental consumption via `remove_prefix(n)` and - `remove_suffix(n)`. + and supports incremental consumption via `remove_prefix(n)`. @par Return Value An object of unspecified type satisfying the @ref Slice concept. @@ -34,13 +33,24 @@ namespace capy { additionally models @ref MutableSlice. @par Lifetime - The returned object holds a non-owning reference to data within - `seq`. `seq` must remain valid until the returned object is - destroyed. Iterators and buffer descriptors obtained through - `data()` follow the same invalidation rules as those of `seq`. + The returned slice is associated with `seq` as its underlying + buffer sequence. `seq` — and the memory referenced by its buffer + descriptors — must remain valid for as long as the slice, or + any buffer sequence obtained from its `data()`, is in use. + Passing a temporary buffer sequence to `buffer_slice` produces + a dangling slice. + + The buffer sequence returned by `data()` is independent of the + slice object: subsequent operations on the slice (mutation, + copy, move, destruction) do not invalidate an already-obtained + `data()` view. It remains valid for as long as `seq` is valid. + + Iterators and buffer descriptors obtained through `data()` + follow the same invalidation rules as those of `seq`. @par Parameters - @li `seq` The underlying buffer sequence. + @li `seq` The underlying buffer sequence. Must outlive the + returned slice and any `data()` view obtained from it. @li `offset` Number of bytes to skip from the start of `seq`. Clamped to `buffer_size(seq)`. @li `length` Maximum number of bytes the slice will expose, @@ -83,6 +93,32 @@ buffer_slice( return detail::slice_impl(seq, offset, length); } +/** Deleted overload that rejects rvalue arguments at compile time. + + Because the returned slice's validity depends on the underlying + buffer sequence remaining alive, calling `buffer_slice` with a + temporary buffer sequence would produce an immediately dangling + slice. This overload makes such calls ill-formed, surfacing the + lifetime error at compile time rather than as runtime UB. + + To slice a buffer sequence produced as a temporary, hoist it + into a named variable first: + + @code + auto bufs = some_dynamic_buffer.data(); // named, lives in scope + auto s = buffer_slice( bufs ); // OK + @endcode +*/ +template + requires MutableBufferSequence + || ConstBufferSequence +auto +buffer_slice( + BufferSequence const&& seq, + std::size_t offset = 0, + std::size_t length = + (std::numeric_limits::max)()) = delete; + } // namespace capy } // namespace boost diff --git a/include/boost/capy/buffers/circular_dynamic_buffer.hpp b/include/boost/capy/buffers/circular_dynamic_buffer.hpp index 33fa20be8..0ede578d0 100644 --- a/include/boost/capy/buffers/circular_dynamic_buffer.hpp +++ b/include/boost/capy/buffers/circular_dynamic_buffer.hpp @@ -11,9 +11,11 @@ #define BOOST_CAPY_BUFFERS_CIRCULAR_DYNAMIC_BUFFER_HPP #include -#include +#include #include +#include + namespace boost { namespace capy { @@ -62,10 +64,10 @@ class circular_dynamic_buffer using is_dynamic_buffer_adapter = void; /// The ConstBufferSequence type for readable bytes. - using const_buffers_type = const_buffer_pair; + using const_buffers_type = std::array; /// The MutableBufferSequence type for writable bytes. - using mutable_buffers_type = mutable_buffer_pair; + using mutable_buffers_type = std::array; /// Construct an empty circular buffer with zero capacity. circular_dynamic_buffer() = default; diff --git a/include/boost/capy/buffers/slice.hpp b/include/boost/capy/buffers/slice.hpp deleted file mode 100644 index 9caa608e1..000000000 --- a/include/boost/capy/buffers/slice.hpp +++ /dev/null @@ -1,563 +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 -// - -#ifndef BOOST_CAPY_BUFFERS_SLICE_HPP -#define BOOST_CAPY_BUFFERS_SLICE_HPP - -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace capy { - -template class slice_of; - -namespace detail { - -template -struct has_tag_invoke : std::false_type {}; - -template -struct has_tag_invoke(), - std::declval(), - std::declval(), - std::declval()))> - : std::true_type {}; - -} // detail - -/** Alias for the type representing a slice of T -*/ -template -using slice_type = std::conditional_t< - detail::has_tag_invoke::value, - T, slice_of>; - -/** A view of a sub-range of a buffer sequence. - - This class wraps a buffer sequence and presents a - contiguous byte sub-range by adjusting the first and - last buffers. The prefix and suffix can be removed or - kept using the free functions @ref keep_prefix, - @ref remove_prefix, etc. - - The wrapped sequence is stored by value. The underlying - buffer memory must remain valid for the lifetime of the - slice. - - @par Thread Safety - Distinct objects: Safe. - Shared objects: Unsafe. - - @par Example - @code - mutable_buffer buf(data, 100); - auto s = prefix(buf, 50); // first 50 bytes - remove_prefix(s, 10); // now bytes 10..49 - @endcode - - @tparam BufferSequence The buffer sequence type, stored - by value. Must satisfy @ref ConstBufferSequence. - - @see keep_prefix, remove_prefix, prefix, sans_prefix -*/ -template -class slice_of -{ - static_assert(!std::is_const_v, - "BufferSequence can't be const"); - - static_assert(!std::is_reference_v, - "BufferSequence can't be a reference"); - - using iter_type = decltype( - std::declval().begin()); - - using difference_type = - typename std::iterator_traits::difference_type; - - BufferSequence bs_; - difference_type begin_ = 0; // index of first buffer in sequence - difference_type end_ = 0; // 1 + index of last buffer in sequence - std::size_t len_ = 0; // length of bs_ - std::size_t size_ = 0; // total bytes - std::size_t prefix_ = 0; // used prefix bytes - std::size_t suffix_ = 0; // used suffix bytes - -public: - /** The type of values returned by iterators - */ - using value_type = std::conditional_t< - MutableBufferSequence, - mutable_buffer, const_buffer>; - - /** The type of returned iterators - */ - class const_iterator - { - iter_type it_; - // VFALCO we could just point back to - // the original sequence to save size - std::size_t prefix_ = 0; - std::size_t suffix_ = 0; - std::size_t i_ = 0; - std::size_t n_ = 0; - - friend class slice_of; - - const_iterator( - iter_type it, - std::size_t prefix__, - std::size_t suffix__, - std::size_t i, - std::size_t n) noexcept - : it_(it) - , prefix_(prefix__) - , suffix_(suffix__) - , i_(i) - , n_(n) - { - // n_ is the index of the end iterator - } - - public: - using value_type = typename slice_of::value_type; - using reference = value_type; - using pointer = void; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - using iterator_concept = std::bidirectional_iterator_tag; - - const_iterator() = default; - - /// Test for equality. - bool - operator==( - const_iterator const& other) const noexcept - { - return - it_ == other.it_ && - prefix_ == other.prefix_ && - suffix_ == other.suffix_ && - i_ == other.i_ && - n_ == other.n_; - } - - /// Test for inequality. - bool - operator!=( - const_iterator const& other) const noexcept - { - return !(*this == other); - } - - /// Return the current buffer, adjusted for prefix/suffix. - reference - operator*() const noexcept - { - value_type v = *it_; - using P = std::conditional_t< - MutableBufferSequence, - char*, char const*>; - auto p = reinterpret_cast

(v.data()); - auto n = v.size(); - if(i_ == 0) - { - p += prefix_; - n -= prefix_; - } - if(i_ == n_ - 1) - n -= suffix_; - return value_type(p, n); - } - - /// Advance to the next element. - const_iterator& - operator++() noexcept - { - BOOST_CAPY_ASSERT(i_ < n_); - ++it_; - ++i_; - return *this; - } - - /// Advance to the next element (postfix). - const_iterator - operator++(int) noexcept - { - auto temp = *this; - ++(*this); - return temp; - } - - /// Move to the previous element. - const_iterator& - operator--() noexcept - { - BOOST_CAPY_ASSERT(i_ > 0); - --it_; - --i_; - return *this; - } - - /// Move to the previous element (postfix). - const_iterator - operator--(int) noexcept - { - auto temp = *this; - --(*this); - return temp; - } - }; - - /** Constructor - */ - slice_of() = default; - - /** Constructor - */ - slice_of( - BufferSequence const& bs) - : bs_(bs) - { - iter_type it = capy::begin(bs_); - iter_type eit = capy::end(bs_); - begin_ = 0; - end_ = std::distance(it, eit); - while(it != eit) - { - value_type b(*it); - size_ += b.size(); - ++len_; - ++it; - } - } - - /** Return an iterator to the beginning of the sequence - */ - const_iterator - begin() const noexcept - { - return const_iterator( - begin_iter_impl(), prefix_, suffix_, 0, len_); - } - - /** Return an iterator to the end of the sequence - */ - const_iterator - end() const noexcept - { - return const_iterator( - end_iter_impl(), prefix_, suffix_, len_, len_); - } - - /// Slice customization point for this type. - friend - void - tag_invoke( - slice_tag const&, - slice_of& bs, - slice_how how, - std::size_t n) - { - bs.slice_impl(how, n); - } - -private: - iter_type - begin_iter_impl() const noexcept - { - iter_type it = capy::begin(bs_); - std::advance(it, begin_); - return it; - } - - iter_type - end_iter_impl() const noexcept - { - iter_type it = capy::begin(bs_); - std::advance(it, end_); - return it; - } - - void - remove_prefix_impl( - std::size_t n) - { - if(n > size_) - n = size_; - - // nice hack to simplify the loop (M. Nejati) - n += prefix_; - size_ += prefix_; - prefix_ = 0; - - iter_type it = begin_iter_impl(); - - while(n > 0 && begin_ != end_) - { - value_type b = *it; - if(n < b.size()) - { - prefix_ = n; - size_ -= n; - break; - } - n -= b.size(); - size_ -= b.size(); - ++begin_; - ++it; - --len_; - } - } - - void - remove_suffix_impl( - std::size_t n) - { - if(size_ == 0) - { - BOOST_CAPY_ASSERT(begin_ == end_); - return; - } - BOOST_CAPY_ASSERT(begin_ != end_); - - if(n > size_) - n = size_; - - n += suffix_; - size_ += suffix_; - suffix_ = 0; - - iter_type bit = begin_iter_impl(); - iter_type it = end_iter_impl(); - it--; - - while(it != bit) - { - value_type b = *it; - if(n < b.size()) - { - suffix_ = n; - size_ -= n; - return; - } - n -= b.size(); - size_ -= b.size(); - --it; - --end_; - --len_; - } - value_type b = *it; - auto m = b.size() - prefix_; - if(n < m) - { - suffix_ = n; - size_ -= n; - return; - } - end_ = begin_; - len_ = 0; - size_ = 0; - } - - void - keep_prefix_impl( - std::size_t n) - { - if(n >= size_) - return; - if(n == 0) - { - end_ = begin_; - len_ = 0; - size_ = 0; - return; - } - remove_suffix_impl(size_ - n); - } - - void - keep_suffix_impl( - std::size_t n) - { - if(n >= size_) - return; - if(n == 0) - { - begin_ = end_; - len_ = 0; - size_ = 0; - return; - } - remove_prefix_impl(size_ - n); - } - - void - slice_impl( - slice_how how, - std::size_t n) - { - switch(how) - { - case slice_how::remove_prefix: - { - remove_prefix_impl(n); - break; - } - case slice_how::keep_prefix: - { - keep_prefix_impl(n); - break; - } - } - } -}; - -// in-place modify return value -// ----------------------------- -// keep_prefix* prefix -// keep_suffix suffix -// remove_prefix* sans_prefix -// remove_suffix sans_suffix - -/** Remove all but the first `n` bytes from a buffer sequence -*/ -constexpr struct keep_prefix_mrdocs_workaround_t -{ - template - requires detail::has_tag_invoke::value - void operator()( - BufferSequence& bs, - std::size_t n) const - { - tag_invoke(slice_tag{}, bs, slice_how::keep_prefix, n); - } -} const keep_prefix{}; - -/** Remove all but the last `n` bytes from a buffer sequence -*/ -constexpr struct keep_suffix_mrdocs_workaround_t -{ - template - requires detail::has_tag_invoke::value - void operator()( - BufferSequence& bs, - std::size_t n) const - { - auto n0 = buffer_size(bs); - if(n < n0) - tag_invoke(slice_tag{}, bs, slice_how::remove_prefix, n0 - n); - } -} const keep_suffix{}; - -/** Remove `n` bytes from the beginning of a buffer sequence -*/ -constexpr struct remove_prefix_mrdocs_workaround_t -{ - template - requires detail::has_tag_invoke::value - void operator()( - BufferSequence& bs, - std::size_t n) const - { - tag_invoke(slice_tag{}, bs, slice_how::remove_prefix, n); - } -} const remove_prefix{}; - -/** Remove `n` bytes from the end of a buffer sequence -*/ -constexpr struct remove_suffix_mrdocs_workaround_t -{ - template - requires detail::has_tag_invoke::value - void operator()( - BufferSequence& bs, - std::size_t n) const - { - auto n0 = buffer_size(bs); - if(n > 0) - { - if( n > n0) - n = n0; - tag_invoke(slice_tag{}, bs, slice_how::keep_prefix, n0 - n); - } - } -} const remove_suffix{}; - -/** Return a sequence representing the first `n` bytes of a buffer sequence -*/ -constexpr struct prefix_mrdocs_workaround_t -{ - template - slice_type operator()( - BufferSequence const& bs, - std::size_t n) const noexcept - { - slice_type result(bs); - keep_prefix(result, n); - return result; - } -} prefix{}; - -/** Return a sequence representing the last `n` bytes of a buffer sequence -*/ -constexpr struct suffix_mrdocs_workaround_t -{ - template - slice_type operator()( - BufferSequence const& bs, - std::size_t n) const noexcept - { - slice_type result(bs); - keep_suffix(result, n); - return result; - } -} suffix{}; - -/** Return a sequence representing all but the first `n` bytes of a buffer sequence -*/ -constexpr struct sans_prefix_mrdocs_workaround_t -{ - template - slice_type operator()( - BufferSequence const& bs, - std::size_t n) const noexcept - { - slice_type result(bs); - remove_prefix(result, n); - return result; - } -} sans_prefix{}; - -/** Return a sequence representing all but the last `n` bytes of a buffer sequence -*/ -constexpr struct sans_suffix_mrdocs_workaround_t -{ - template - slice_type operator()( - BufferSequence const& bs, - std::size_t n) const noexcept - { - slice_type result(bs); - remove_suffix(result, n); - return result; - } -} sans_suffix{}; - -} // capy -} // boost - -#endif diff --git a/include/boost/capy/concept/slice.hpp b/include/boost/capy/concept/slice.hpp index f552710ec..928d2e190 100644 --- a/include/boost/capy/concept/slice.hpp +++ b/include/boost/capy/concept/slice.hpp @@ -31,16 +31,25 @@ namespace capy { @par Semantic Requirements @li `s.data()` returns a buffer sequence view of the slice's current - live bytes. The view is valid until the next mutating operation - on `s` or `s`'s destruction. + live bytes. @li `s.remove_prefix(n)` makes the first `min(n, total_live_bytes)` bytes no longer part of the slice. @par Lifetime - The underlying buffer sequence referenced by the slice must outlive - any `Slice`-modeling object that views it. Iterators and buffer - descriptors obtained through `data()` follow the same invalidation - rules as those of the underlying sequence. + A `Slice` is associated, on construction, with an underlying + buffer sequence. The slice is valid for as long as that sequence + — and the memory referenced by its buffer descriptors — remains + valid. Operating on a slice whose underlying sequence is no + longer valid is undefined behavior. + + The buffer sequence returned by `data()` is independent of the + slice object: subsequent operations on the slice + (`remove_prefix`, copy, move, destruction) do not invalidate + an already-obtained `data()` view. It remains valid for as + long as the slice's underlying buffer sequence is valid. + + Buffer descriptors obtained through `data()` follow the same + invalidation rules as those of the underlying sequence. @par Concrete Types Objects modeling `Slice` are produced by the @ref buffer_slice free diff --git a/include/boost/capy/buffers/buffer_array.hpp b/include/boost/capy/detail/buffer_array.hpp similarity index 81% rename from include/boost/capy/buffers/buffer_array.hpp rename to include/boost/capy/detail/buffer_array.hpp index dbf50c0c6..2d0155e14 100644 --- a/include/boost/capy/buffers/buffer_array.hpp +++ b/include/boost/capy/detail/buffer_array.hpp @@ -7,8 +7,8 @@ // Official repository: https://github.com/cppalliance/capy // -#ifndef BOOST_CAPY_BUFFERS_BUFFER_ARRAY_HPP -#define BOOST_CAPY_BUFFERS_BUFFER_ARRAY_HPP +#ifndef BOOST_CAPY_DETAIL_BUFFER_ARRAY_HPP +#define BOOST_CAPY_DETAIL_BUFFER_ARRAY_HPP #include #include @@ -21,43 +21,8 @@ namespace boost { namespace capy { - namespace detail { -BOOST_CAPY_DECL -void -buffer_array_remove_prefix( - const_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept; - -BOOST_CAPY_DECL -void -buffer_array_remove_prefix( - mutable_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept; - -BOOST_CAPY_DECL -void -buffer_array_keep_prefix( - const_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept; - -BOOST_CAPY_DECL -void -buffer_array_keep_prefix( - mutable_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept; - -} // namespace detail - /** A buffer sequence holding up to N buffers. This class template stores a fixed-capacity array of buffer @@ -69,7 +34,7 @@ buffer_array_keep_prefix( @code void process(ConstBufferSequence auto const& buffers) { - const_buffer_array<4> bufs(buffers); + detail::const_buffer_array<4> bufs(buffers); // use bufs.begin(), bufs.end(), bufs.to_span() } @endcode @@ -332,56 +297,10 @@ class buffer_array /** Return the total byte count in O(1). */ - friend std::size_t - tag_invoke( - size_tag const&, - buffer_array const& ba) noexcept - { - return ba.size_; - } - - /** Slice customization point. - */ - friend - void - tag_invoke( - slice_tag const&, - buffer_array& ba, - slice_how how, - std::size_t n) noexcept - { - ba.slice_impl(how, n); - } - -private: - void - slice_impl( - slice_how how, - std::size_t n) noexcept + byte_size() const noexcept { - switch(how) - { - case slice_how::remove_prefix: - remove_prefix_impl(n); - break; - - case slice_how::keep_prefix: - keep_prefix_impl(n); - break; - } - } - - void - remove_prefix_impl(std::size_t n) noexcept - { - detail::buffer_array_remove_prefix(arr_, &n_, &size_, n); - } - - void - keep_prefix_impl(std::size_t n) noexcept - { - detail::buffer_array_keep_prefix(arr_, &n_, &size_, n); + return size_; } }; @@ -399,6 +318,7 @@ using const_buffer_array = buffer_array; template using mutable_buffer_array = buffer_array; +} // namespace detail } // namespace capy } // namespace boost diff --git a/include/boost/capy/detail/slice_impl.hpp b/include/boost/capy/detail/slice_impl.hpp index 615ef780b..86d77c943 100644 --- a/include/boost/capy/detail/slice_impl.hpp +++ b/include/boost/capy/detail/slice_impl.hpp @@ -65,20 +65,20 @@ class slice_impl static buffer_type adjust_buffer( buffer_type const& buf, - std::size_t front, - std::size_t back) noexcept + std::size_t front_n, + std::size_t back_n) noexcept { if constexpr (std::is_same_v) { return mutable_buffer( - static_cast(buf.data()) + front, - buf.size() - front - back); + static_cast(buf.data()) + front_n, + buf.size() - front_n - back_n); } else { return const_buffer( - static_cast(buf.data()) + front, - buf.size() - front - back); + static_cast(buf.data()) + front_n, + buf.size() - front_n - back_n); } } @@ -136,11 +136,11 @@ class slice_impl value_type operator*() const noexcept { buffer_type buf = *cur_; - auto front = (cur_ == anchor_first_) ? front_skip_ : 0; + auto front_n = (cur_ == anchor_first_) ? front_skip_ : 0; auto next = cur_; ++next; - auto back = (next == anchor_last_) ? back_skip_ : 0; - return adjust_buffer(buf, front, back); + auto back_n = (next == anchor_last_) ? back_skip_ : 0; + return adjust_buffer(buf, front_n, back_n); } const_iterator& operator++() noexcept diff --git a/include/boost/capy/io/any_buffer_source.hpp b/include/boost/capy/io/any_buffer_source.hpp index a5dc2a609..bdce6e542 100644 --- a/include/boost/capy/io/any_buffer_source.hpp +++ b/include/boost/capy/io/any_buffer_source.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/include/boost/capy/io/any_read_source.hpp b/include/boost/capy/io/any_read_source.hpp index e07d2db0d..5f441aa54 100644 --- a/include/boost/capy/io/any_read_source.hpp +++ b/include/boost/capy/io/any_read_source.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -472,7 +472,7 @@ any_read_source::read_some(MB buffers) struct awaitable { any_read_source* self_; - mutable_buffer_array ba_; + detail::mutable_buffer_array ba_; awaitable(any_read_source* self, MB const& buffers) : self_(self) diff --git a/include/boost/capy/io/any_read_stream.hpp b/include/boost/capy/io/any_read_stream.hpp index 27df88d11..ddbcc3b23 100644 --- a/include/boost/capy/io/any_read_stream.hpp +++ b/include/boost/capy/io/any_read_stream.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -380,7 +380,7 @@ any_read_stream::read_some(MB buffers) struct awaitable { any_read_stream* self_; - mutable_buffer_array ba_; + detail::mutable_buffer_array ba_; bool await_ready() @@ -417,7 +417,7 @@ any_read_stream::read_some(MB buffers) } }; return awaitable{this, - mutable_buffer_array(buffers)}; + detail::mutable_buffer_array(buffers)}; } } // namespace capy diff --git a/include/boost/capy/io/any_write_sink.hpp b/include/boost/capy/io/any_write_sink.hpp index b848c7c64..1cdfd43dd 100644 --- a/include/boost/capy/io/any_write_sink.hpp +++ b/include/boost/capy/io/any_write_sink.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -798,7 +798,7 @@ any_write_sink::write_some(CB buffers) struct awaitable { any_write_sink* self_; - const_buffer_array ba_; + detail::const_buffer_array ba_; awaitable( any_write_sink* self, diff --git a/include/boost/capy/io/any_write_stream.hpp b/include/boost/capy/io/any_write_stream.hpp index f775ee425..40088127b 100644 --- a/include/boost/capy/io/any_write_stream.hpp +++ b/include/boost/capy/io/any_write_stream.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -379,7 +379,7 @@ any_write_stream::write_some(CB buffers) struct awaitable { any_write_stream* self_; - const_buffer_array ba_; + detail::const_buffer_array ba_; awaitable( any_write_stream* self, diff --git a/include/boost/capy/test/bufgrind.hpp b/include/boost/capy/test/bufgrind.hpp index e92b5267d..48446ad76 100644 --- a/include/boost/capy/test/bufgrind.hpp +++ b/include/boost/capy/test/bufgrind.hpp @@ -12,12 +12,13 @@ #include #include -#include +#include #include #include #include #include +#include #include namespace boost { @@ -32,9 +33,12 @@ namespace test { that allows `co_await` between iterations. The split type automatically preserves mutability: passing a - `MutableBufferSequence` yields mutable slices, while passing a - `ConstBufferSequence` yields const slices. This is handled - automatically through `slice_type`. + `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. @par Thread Safety Not thread-safe. @@ -50,8 +54,8 @@ namespace test { bufgrind bg( cb ); while( bg ) { auto [b1, b2] = co_await bg.next(); - // b1 contains first N bytes - // b2 contains remaining bytes + // b1 contains first N bytes (as a buffer sequence) + // b2 contains remaining bytes (as a buffer sequence) // concatenating b1 + b2 equals original co_await some_async_operation( b1, b2 ); } @@ -81,7 +85,7 @@ namespace test { } @endcode - @see prefix, sans_prefix, slice_type + @see buffer_slice */ template class bufgrind @@ -92,8 +96,13 @@ class bufgrind std::size_t pos_ = 0; public: - /// The type returned by @ref next. - using split_type = std::pair, slice_type>; + /// The slice type produced for each half of a split. + using slice_type = std::decay_t< + decltype(buffer_slice(std::declval()))>; + + /// The type returned by @ref next. Each half is a Slice; use + /// `.data()` to obtain the buffer sequence view. + using split_type = std::pair; /** Construct a buffer grinder. @@ -135,13 +144,15 @@ class bufgrind split_type await_resume() { - auto b1 = prefix(self_->bs_, self_->pos_); - auto b2 = sans_prefix(self_->bs_, self_->pos_); + split_type result{ + buffer_slice(self_->bs_, 0, self_->pos_), + buffer_slice(self_->bs_, self_->pos_) + }; if(self_->pos_ < self_->size_) self_->pos_ = (std::min)(self_->pos_ + self_->step_, self_->size_); else ++self_->pos_; - return {std::move(b1), std::move(b2)}; + return result; } }; diff --git a/src/buffers/buffer_array.cpp b/src/buffers/buffer_array.cpp deleted file mode 100644 index 03af25cae..000000000 --- a/src/buffers/buffer_array.cpp +++ /dev/null @@ -1,150 +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 -// - -#include - -namespace boost { -namespace capy { -namespace detail { - -namespace { - -template -void -do_remove_prefix( - Buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept -{ - if(n >= *total_size) - { - while(*count > 0) - arr[--(*count)].~Buffer(); - *total_size = 0; - return; - } - - std::size_t i = 0; - while(i < *count && n > 0) - { - auto& b = arr[i]; - if(n < b.size()) - { - b += n; - *total_size -= n; - break; - } - n -= b.size(); - *total_size -= b.size(); - b.~Buffer(); - ++i; - } - - // Compact: move remaining buffers to front - if(i > 0) - { - std::size_t j = 0; - while(i < *count) - { - arr[j] = arr[i]; - arr[i].~Buffer(); - ++i; - ++j; - } - *count = j; - } -} - -template -void -do_keep_prefix( - Buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept -{ - if(n >= *total_size) - return; - - if(n == 0) - { - while(*count > 0) - arr[--(*count)].~Buffer(); - *total_size = 0; - return; - } - - std::size_t remaining = n; - std::size_t i = 0; - while(i < *count && remaining > 0) - { - auto& b = arr[i]; - if(remaining < b.size()) - { - b = Buffer(b.data(), remaining); - ++i; - break; - } - remaining -= b.size(); - ++i; - } - - // Destruct elements beyond the new count - while(*count > i) - arr[--(*count)].~Buffer(); - - *total_size = n; -} - -} // anonymous namespace - -void -buffer_array_remove_prefix( - const_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept -{ - do_remove_prefix(arr, count, total_size, n); -} - -void -buffer_array_remove_prefix( - mutable_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept -{ - do_remove_prefix(arr, count, total_size, n); -} - -void -buffer_array_keep_prefix( - const_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept -{ - do_keep_prefix(arr, count, total_size, n); -} - -void -buffer_array_keep_prefix( - mutable_buffer* arr, - std::size_t* count, - std::size_t* total_size, - std::size_t n) noexcept -{ - do_keep_prefix(arr, count, total_size, n); -} - -} // namespace detail -} // namespace capy -} // namespace boost diff --git a/src/buffers/buffer_pair.cpp b/src/buffers/buffer_pair.cpp deleted file mode 100644 index 13aeef5fa..000000000 --- a/src/buffers/buffer_pair.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2023 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 -// - -#include -#include - -namespace boost { -namespace capy { - -void -tag_invoke( - slice_tag const&, - const_buffer_pair& bs, - slice_how how, - std::size_t n) noexcept -{ - switch(how) - { - case slice_how::remove_prefix: - { - auto p = &bs[0]; - if(n < p->size()) - { - remove_prefix(*p, n); - return; - } - n -= p->size(); - *p = bs[1]; - bs[1] = {}; - remove_prefix(*p, n); - return; - } - - case slice_how::keep_prefix: - { - auto p = &bs[0]; - if(n <= p->size()) - { - keep_prefix(*p, n); - bs[1] = {}; - return; - } - n -= p->size(); - ++p; - keep_prefix(*p, n); - return; - } - } -} - -void -tag_invoke( - slice_tag const&, - mutable_buffer_pair& bs, - slice_how how, - std::size_t n) noexcept -{ - switch(how) - { - case slice_how::remove_prefix: - { - auto p = &bs[0]; - if(n < p->size()) - { - remove_prefix(*p, n); - return; - } - n -= p->size(); - *p = bs[1]; - bs[1] = {}; - remove_prefix(*p, n); - return; - } - - case slice_how::keep_prefix: - { - auto p = &bs[0]; - if(n <= p->size()) - { - keep_prefix(*p, n); - bs[1] = {}; - return; - } - n -= p->size(); - ++p; - keep_prefix(*p, n); - return; - } - } -} - -} // capy -} // boost diff --git a/test/unit/buffers/asio.cpp b/test/unit/buffers/asio.cpp index 2be261807..c978f75ce 100644 --- a/test/unit/buffers/asio.cpp +++ b/test/unit/buffers/asio.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include @@ -28,9 +28,9 @@ namespace capy { // to_asio result satisfies asio buffer sequence traits using to_asio_const_t = decltype( - to_asio(std::declval const&>())); + to_asio(std::declval const&>())); using to_asio_mutable_t = decltype( - to_asio(std::declval const&>())); + to_asio(std::declval const&>())); static_assert(asio::is_const_buffer_sequence::value); static_assert(asio::is_const_buffer_sequence::value); @@ -117,8 +117,8 @@ struct asio_test { char d1[] = "abc"; char d2[] = "defgh"; - mutable_buffer_array<4> bufs; - bufs = mutable_buffer_array<4>( + detail::mutable_buffer_array<4> bufs; + bufs = detail::mutable_buffer_array<4>( std::array{{ mutable_buffer(d1, 3), mutable_buffer(d2, 5) @@ -292,8 +292,8 @@ struct asio_test test_move_semantics() { char d1[] = "abc"; - mutable_buffer_array<4> bufs; - bufs = mutable_buffer_array<4>( + detail::mutable_buffer_array<4> bufs; + bufs = detail::mutable_buffer_array<4>( mutable_buffer(d1, 3)); auto adapted = to_asio(std::move(bufs)); diff --git a/test/unit/buffers/buffer.cpp b/test/unit/buffers/buffer.cpp index 90329f768..dfda2e296 100644 --- a/test/unit/buffers/buffer.cpp +++ b/test/unit/buffers/buffer.cpp @@ -88,15 +88,15 @@ static_assert(std::ranges::bidirectional_range>); static_assert(std::ranges::random_access_range>); static_assert(std::ranges::contiguous_range>); -// std::ranges concepts for const_buffer_pair / mutable_buffer_pair +// std::ranges concepts for 2-element buffer arrays -static_assert(std::ranges::range); -static_assert(std::ranges::bidirectional_range); -static_assert(std::ranges::random_access_range); +static_assert(std::ranges::range>); +static_assert(std::ranges::bidirectional_range>); +static_assert(std::ranges::random_access_range>); -static_assert(std::ranges::range); -static_assert(std::ranges::bidirectional_range); -static_assert(std::ranges::random_access_range); +static_assert(std::ranges::range>); +static_assert(std::ranges::bidirectional_range>); +static_assert(std::ranges::random_access_range>); // std::views producing valid ConstBufferSequence @@ -178,9 +178,9 @@ struct fixt }; template<> -struct fixt +struct fixt> { - const_buffer_pair t; + std::array t; fixt(std::string_view pat) : t{{ {buf(pat.substr(0, 3))}, {buf(pat.substr(3))} }} { @@ -188,10 +188,10 @@ struct fixt }; template<> -struct fixt +struct fixt> { char data[64]; - mutable_buffer_pair t; + std::array t; fixt(std::string_view pat) : t{{{data,3}, {data+3, pat.size()-3}}} { @@ -316,7 +316,8 @@ struct buffer_test char data[64]; mutable_buffer mb(data, sizeof(data)); fixt f(pat); - keep_prefix(mb, buffer_copy(mb, f.t)); + auto const n = buffer_copy(mb, f.t); + mb = mutable_buffer(mb.data(), n); BOOST_TEST_EQ(test::make_string(mb), pat); } } @@ -325,8 +326,8 @@ struct buffer_test { testBuffer(); testBuffer(); - testBuffer(); - testBuffer(); + testBuffer>(); + testBuffer>(); testBuffer>(); testBuffer>(); testBuffer>(); @@ -483,17 +484,17 @@ struct buffer_test // empty buffer_pair (both empty) { - const_buffer_pair cbp{{ {data, 0}, {data, 0} }}; + std::array cbp{{ {data, 0}, {data, 0} }}; BOOST_TEST(buffer_empty(cbp)); } // non-empty buffer_pair (one non-empty) { - const_buffer_pair cbp{{ {data, 0}, {data, 3} }}; + std::array cbp{{ {data, 0}, {data, 3} }}; BOOST_TEST(! buffer_empty(cbp)); } { - const_buffer_pair cbp{{ {data, 3}, {data, 0} }}; + std::array cbp{{ {data, 3}, {data, 0} }}; BOOST_TEST(! buffer_empty(cbp)); } diff --git a/test/unit/buffers/buffer_copy.cpp b/test/unit/buffers/buffer_copy.cpp index c289d36aa..01a80130f 100644 --- a/test/unit/buffers/buffer_copy.cpp +++ b/test/unit/buffers/buffer_copy.cpp @@ -10,7 +10,7 @@ // Test that header file is self-contained. #include -#include +#include #include #include "test_buffers.hpp" @@ -34,12 +34,12 @@ struct buffer_copy_test for(std::size_t k = 0; k < N + 2; ++k) { - const_buffer_pair p0{{ + std::array p0{{ const_buffer(s.data(), i), const_buffer(s.data() + i, N - i) }}; char tmp[13]; std::memset(tmp, 0, sizeof(tmp)); - mutable_buffer_pair p1{{ + std::array p1{{ mutable_buffer(tmp, j), mutable_buffer(tmp + j, N - j) }}; auto const n = buffer_copy( diff --git a/test/unit/buffers/buffer_pair.cpp b/test/unit/buffers/buffer_pair.cpp deleted file mode 100644 index 83f30d108..000000000 --- a/test/unit/buffers/buffer_pair.cpp +++ /dev/null @@ -1,197 +0,0 @@ -// -// Copyright (c) 2023 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" - -namespace boost { -namespace capy { - -struct buffer_pair_test -{ - void - testConstPair() - { - auto const& pat = test_pattern(); - - // const_buffer_pair() - { - const_buffer_pair cb; - BOOST_TEST_EQ( - buffer_size(cb), 0); - } - - // const_buffer_pair( - // const_buffer_pair const&), - // const_buffer_pair( - // const_buffer const&) - // const_buffer const&) - { - for(std::size_t i = 0; - i <= pat.size(); ++i) - { - const_buffer_pair cb0({{ - { &pat[0], i }, - { &pat[i], pat.size() - i }}}); - const_buffer_pair cb1(cb0); - BOOST_TEST_EQ( - test::make_string(cb0), pat); - BOOST_TEST_EQ( - test::make_string(cb0), - test::make_string(cb1)); - BOOST_TEST_EQ( - cb0[0].data(), cb1[0].data()); - BOOST_TEST_EQ( - cb0[1].size(), cb1[1].size()); - auto const& ccb0 = cb0; - auto const& ccb1 = cb1; - BOOST_TEST_EQ( - ccb0[0].data(), ccb1[0].data()); - BOOST_TEST_EQ( - ccb0[1].size(), ccb1[1].size()); - } - } - - // operator=(const_buffer_pair const&) - { - for(std::size_t i = 0; - i <= pat.size(); ++i) - { - const_buffer_pair cb0({{ - { &pat[0], i }, - { &pat[i], pat.size() - i }}}); - const_buffer_pair cb1; - cb1 = cb0; - BOOST_TEST_EQ( - test::make_string(cb0), pat); - BOOST_TEST_EQ( - test::make_string(cb0), - test::make_string(cb1)); - } - } - - { - for(std::size_t i = 0; - i <= pat.size(); ++i) - { - const_buffer_pair cb({{ - { &pat[0], i }, - { &pat[i], pat.size() - i }}}); - test::check_sequence(cb, pat); - } - } - } - - void - testMutablePair() - { - std::string pat = test_pattern(); - - // mutable_buffer_pair() - { - mutable_buffer_pair mb; - BOOST_TEST_EQ(buffer_size(mb), 0); - } - - // mutable_buffer_pair( - // mutable_buffer_pair const&), - // mutable_buffer_pair( - // mutable_buffer const&) - // mutable_buffer const&) - { - for(std::size_t i = 0; - i <= pat.size(); ++i) - { - mutable_buffer_pair mb0({{ - { &pat[0], i }, - { &pat[i], pat.size() - i }}}); - mutable_buffer_pair mb1(mb0); - BOOST_TEST_EQ( - test::make_string(mb0), pat); - BOOST_TEST_EQ( - test::make_string(mb0), - test::make_string(mb1)); - BOOST_TEST_EQ( - mb0[0].data(), mb1[0].data()); - BOOST_TEST_EQ( - mb0[1].size(), mb1[1].size()); - auto const& cmb0 = mb0; - auto const& cmb1 = mb1; - BOOST_TEST_EQ( - cmb0[0].data(), cmb1[0].data()); - BOOST_TEST_EQ( - cmb0[1].size(), cmb1[1].size()); - } - } - - // operator=(mutable_buffer_pair const&) - { - for(std::size_t i = 0; - i <= pat.size(); ++i) - { - mutable_buffer_pair mb0({{ - { &pat[0], i }, - { &pat[i], pat.size() - i }}}); - mutable_buffer_pair mb1; - mb1 = mb0; - BOOST_TEST_EQ( - test::make_string(mb0), pat); - BOOST_TEST_EQ( - test::make_string(mb0), - test::make_string(mb1)); - } - } - - // operator=(mutable_buffer_pair const&) - { - for(std::size_t i = 0; - i <= pat.size(); ++i) - { - auto s = pat; - mutable_buffer_pair b({{ - { &s[0], i }, - { &s[i], s.size() - i }}}); - mutable_buffer_pair mb; - mb = b; - BOOST_TEST_EQ( - test::make_string(mb), pat); - BOOST_TEST_EQ( - test::make_string(mb), - test::make_string(b)); - } - } - - { - for(std::size_t i = 0; - i <= pat.size(); ++i) - { - mutable_buffer_pair cb({{ - { &pat[0], i }, - { &pat[i], pat.size() - i }}}); - test::check_sequence(cb, pat); - } - } - } - - void - run() - { - testConstPair(); - testMutablePair(); - } -}; - -TEST_SUITE( - buffer_pair_test, - "boost.capy.buffers.buffer_pair"); - -} // capy -} // boost diff --git a/test/unit/buffers/circular_dynamic_buffer.cpp b/test/unit/buffers/circular_dynamic_buffer.cpp index e202e7046..9657ceced 100644 --- a/test/unit/buffers/circular_dynamic_buffer.cpp +++ b/test/unit/buffers/circular_dynamic_buffer.cpp @@ -105,16 +105,15 @@ struct circular_dynamic_buffer_test } } - // Helper: total size of a const_buffer_pair + // Helper: total size of a 2-element buffer pair static std::size_t - bp_total_size(const_buffer_pair const& bp) noexcept + bp_total_size(std::array const& bp) noexcept { return bp[0].size() + bp[1].size(); } - // Helper: total size of a mutable_buffer_pair static std::size_t - bp_total_size(mutable_buffer_pair const& bp) noexcept + bp_total_size(std::array const& bp) noexcept { return bp[0].size() + bp[1].size(); } diff --git a/test/unit/buffers/slice.cpp b/test/unit/buffers/slice.cpp deleted file mode 100644 index d1cf1c6a3..000000000 --- a/test/unit/buffers/slice.cpp +++ /dev/null @@ -1,345 +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 -#include -#include - -#include -#include -#include -#include - -#include "test_buffers.hpp" -#include "test_suite.hpp" - -namespace boost { -namespace capy { - -// std::ranges concepts for slice_of - -using slice_of_cbp = slice_of; -using slice_of_mbp = slice_of; -using slice_of_vec = slice_of>; - -static_assert(std::ranges::range); -static_assert(std::ranges::input_range); -static_assert(std::ranges::forward_range); -static_assert(std::ranges::bidirectional_range); - -static_assert(ConstBufferSequence); -static_assert(!MutableBufferSequence); - -static_assert(std::ranges::range); -static_assert(std::ranges::bidirectional_range); - -static_assert(ConstBufferSequence); -static_assert(MutableBufferSequence); - -static_assert(std::ranges::range); -static_assert(std::ranges::bidirectional_range); -static_assert(ConstBufferSequence); - -template< - std::size_t I, - std::size_t N> -void -set( - std::string&, - std::array&) -{ -} - -template< - std::size_t I, - std::size_t N, - class... Args> -void -set( - std::string& s, - std::array& v, - char const* p, - Args const&... args) -{ - std::string_view sv(p); - v[I] = make_buffer(sv); - s.append(sv.data(), sv.size()); - set(s, v, args...); -} - -auto -make_buffers( - std::string&) -> - std::array -{ - return {}; -} - -template< - class... Args> -auto -make_buffers( - std::string& s, - char const* arg0, - Args const&... args) -> - std::array -{ - s = {}; - std::array v; - set<0>(s, v, arg0, args...); - return v; -} - -struct slice_test -{ - static - void - checkStatic() - { - using T = slice_of; - - static_assert(std::is_default_constructible::value); - static_assert(std::is_copy_constructible::value); - static_assert(std::is_move_constructible::value); - static_assert(std::is_copy_assignable::value); - static_assert(std::is_move_assignable::value); - - using U = T::const_iterator; - - static_assert(std::is_default_constructible::value); - static_assert(std::is_copy_constructible::value); - static_assert(std::is_move_constructible::value); - static_assert(std::is_copy_assignable::value); - static_assert(std::is_move_assignable::value); - } - - template - static - void - check( - B const& b, - std::string_view s) - { - auto constexpr M = 1024; - char buf[M]; - if(! BOOST_TEST_LE(buffer_size(b), M)) - return; - if(! BOOST_TEST_EQ(buffer_size(b), s.size())) - return; - auto const n = buffer_copy( - mutable_buffer(buf, M), b); - if(! BOOST_TEST_EQ(n, s.size())) - return; - if(! BOOST_TEST_EQ(std::string_view(buf, n), s)) - return; - - std::string tmp; - test::check_iterators(b, s, tmp); - } - - // Use a vector so that iterator invalidation is observable during testing. - using seq_type = std::vector; - - void - grind_back( - slice_of const& bs0, - std::string_view pat0) - { - auto const n = buffer_size(bs0); - if(! BOOST_TEST_EQ(n, pat0.size())) - return; - for(std::size_t i = 0; i < n; ++i) - { - auto bs = bs0; - auto pat = pat0.substr(0, pat0.size() - i); - remove_suffix(bs, i); - check(bs, pat); - } - // n >= buffer_size - for(std::size_t i = 0; i < 2; ++i) - { - auto bs = bs0; - remove_suffix(bs, n + i); - BOOST_TEST_EQ(buffer_size(bs), 0); - check(bs, ""); - } - } - - void - grind( - slice_of const& bs0, - std::string_view pat0) - { - auto const n = buffer_size(bs0); - if(! BOOST_TEST_EQ(n, pat0.size())) - return; - for(std::size_t i = 0; i < n; ++i) - { - auto bs = bs0; - auto pat = pat0.substr(i); - remove_prefix(bs, i); - check(bs, pat); - grind_back(bs, pat); - } - // n >= buffer_size - for(std::size_t i = 0; i < 2; ++i) - { - auto bs = bs0; - remove_prefix(bs, n + i); - BOOST_TEST_EQ(buffer_size(bs), 0); - check(bs, ""); - } - } - - void - testSansPrefixSingleBuffer() - { - // Test sans_prefix with a single mutable_buffer - { - char data[] = "0123456789"; - mutable_buffer buf(data, 10); - - // sans_prefix(buf, 0) should return the full buffer - auto s0 = sans_prefix(buf, 0); - BOOST_TEST_EQ(buffer_size(s0), 10u); - - // sans_prefix(buf, 3) should skip first 3 bytes - auto s3 = sans_prefix(buf, 3); - BOOST_TEST_EQ(buffer_size(s3), 7u); - BOOST_TEST_EQ( - static_cast( - const_buffer(s3).data())[0], '3'); - - // sans_prefix(buf, 10) should be empty - auto s10 = sans_prefix(buf, 10); - BOOST_TEST_EQ(buffer_size(s10), 0u); - - // sans_prefix(buf, 100) should be empty - auto s100 = sans_prefix(buf, 100); - BOOST_TEST_EQ(buffer_size(s100), 0u); - } - - // Test sans_prefix with a single const_buffer - { - char data[] = "Hello World"; - const_buffer buf(data, 11); - - auto s0 = sans_prefix(buf, 0); - BOOST_TEST_EQ(buffer_size(s0), 11u); - - auto s6 = sans_prefix(buf, 6); - BOOST_TEST_EQ(buffer_size(s6), 5u); - BOOST_TEST_EQ( - static_cast(s6.data())[0], 'W'); - } - } - - void - testSansPrefixBufferSequence() - { - // Test sans_prefix with a vector of buffers - std::string s1 = "ABCD"; - std::string s2 = "EFGH"; - std::string s3 = "IJKL"; - - std::vector bufs = { - const_buffer(s1.data(), s1.size()), - const_buffer(s2.data(), s2.size()), - const_buffer(s3.data(), s3.size()) - }; - - // sans_prefix removing nothing - { - auto result = sans_prefix(bufs, 0); - BOOST_TEST_EQ(buffer_size(result), 12u); - } - - // sans_prefix removing 2 bytes (within first buffer) - { - auto result = sans_prefix(bufs, 2); - BOOST_TEST_EQ(buffer_size(result), 10u); - } - - // sans_prefix removing 5 bytes (crosses buffer boundary) - { - auto result = sans_prefix(bufs, 5); - BOOST_TEST_EQ(buffer_size(result), 7u); - } - - // sans_prefix removing all - { - auto result = sans_prefix(bufs, 12); - BOOST_TEST_EQ(buffer_size(result), 0u); - } - } - - void - testBufferEmptyWithSlice() - { - // Verify buffer_empty works correctly with sliced buffers - { - char data[] = "test"; - mutable_buffer buf(data, 4); - - auto s0 = sans_prefix(buf, 0); - BOOST_TEST(!buffer_empty(s0)); - - auto s4 = sans_prefix(buf, 4); - BOOST_TEST(buffer_empty(s4)); - } - } - - void - testSansPrefixLoop() - { - // Test the pattern used in any_buffer_source::read() - char data[10] = {}; - mutable_buffer buf(data, 10); - - auto dest = sans_prefix(buf, 0); - BOOST_TEST_EQ(buffer_size(dest), 10u); - BOOST_TEST(!buffer_empty(dest)); - - // Simulate consuming 2 bytes - dest = sans_prefix(dest, 2); - BOOST_TEST_EQ(buffer_size(dest), 8u); - BOOST_TEST(!buffer_empty(dest)); - - // Consume remaining - dest = sans_prefix(dest, 8); - BOOST_TEST_EQ(buffer_size(dest), 0u); - BOOST_TEST(buffer_empty(dest)); - } - - void - run() - { - std::string s; - auto a = make_buffers(s, "boost.", "buffers.", "slice_"); - seq_type bs(a.begin(), a.end()); - test::check_sequence(bs, s, true); - //check(bs, s); - //grind(bs, s); - - testSansPrefixSingleBuffer(); - testSansPrefixBufferSequence(); - testBufferEmptyWithSlice(); - testSansPrefixLoop(); - } -}; - -TEST_SUITE( - slice_test, - "boost.capy.buffers.slice"); - -} // capy -} // boost diff --git a/test/unit/buffers/test_buffers.hpp b/test/unit/buffers/test_buffers.hpp index 33261bca1..ff1f25b03 100644 --- a/test/unit/buffers/test_buffers.hpp +++ b/test/unit/buffers/test_buffers.hpp @@ -11,8 +11,8 @@ #define BOOST_CAPY_BUFFERS_TEST_BUFFERS_HPP #include +#include #include -#include #include #include @@ -261,40 +261,39 @@ grind_front( bool deep) { std::string tmp; + std::size_t const total = buffer_size(bs0); for(std::size_t n = 0; n <= pat0.size() + 1; ++n) { { + // remove_prefix: drop the first n bytes auto pat = trimmed_front(pat0, n); - slice_type bs(bs0); - remove_prefix(bs, n); - check_eq(bs, pat); - check_iterators(bs, pat, tmp); + auto bs = buffer_slice(bs0); + bs.remove_prefix(n); + check_eq(bs.data(), pat); + check_iterators(bs.data(), pat, tmp); if(deep) { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } + // Take a copy, blank out the original, and redo the test + auto bsc = bs; + bs = decltype(bs){}; for(std::size_t m = 0; m <= pat.size() + 1; ++m) { auto pat2 = trimmed_front(pat, m); - slice_type bs2(bsc); - remove_prefix(bs2, m); - check_eq(bs2, pat2); + auto bs2 = bsc; + bs2.remove_prefix(m); + check_eq(bs2.data(), pat2); } } } { + // keep_prefix: keep only the first n bytes auto pat = kept_front(pat0, n); - slice_type bs(bs0); - keep_prefix(bs, n); - check_eq(bs, pat); - check_iterators(bs, pat, tmp); + std::size_t const len = (n < total) ? n : total; + auto bs = buffer_slice(bs0, 0, len); + check_eq(bs.data(), pat); + check_iterators(bs.data(), pat, tmp); } } } @@ -307,39 +306,47 @@ grind_back( bool deep) { std::string tmp; + std::size_t const total = buffer_size(bs0); for(std::size_t n = 0; n <= pat0.size() + 1; ++n) { { + // remove_suffix: drop the last n bytes auto pat = trimmed_back(pat0, n); - slice_type bs(bs0); - remove_suffix(bs, n); - check_eq(bs, pat); - check_iterators(bs, pat, tmp); + std::size_t const len = (n < total) ? total - n : 0; + auto bs = buffer_slice(bs0, 0, len); + check_eq(bs.data(), pat); + check_iterators(bs.data(), pat, tmp); if(deep) { - // Take a copy, blank out the original to invalidate any - // iterators, and redo the test - slice_type bsc(bs); - { - slice_type dummy{}; - std::swap(bs, dummy); - } + // Take a copy, blank out the original, and redo the test + auto bsc = bs; + bs = decltype(bs){}; for(std::size_t m = 0; m <= pat.size() + 1; ++m) { auto pat2 = trimmed_back(pat, m); - slice_type bs2(bsc); - remove_suffix(bs2, m); - check_eq(bs2, pat2); + // Drop another m bytes from the back of bsc by + // length-capping a fresh slice of the same data. + std::size_t const len2 = buffer_size(bsc.data()); + std::size_t const new_len = + (m < len2) ? len2 - m : 0; + auto bs2 = bsc; + // Walk forward (current state) and use remove_prefix + // to drop the front; for the back we need a fresh + // slice over the inner-window. Easiest: construct + // a new slice from the original at the right offset/length. + bs2 = buffer_slice(bs0, 0, new_len); + check_eq(bs2.data(), pat2); } } } { + // keep_suffix: keep only the last n bytes auto pat = kept_back(pat0, n); - slice_type bs(bs0); - keep_suffix(bs, n); - check_eq(bs, pat); - check_iterators(bs, pat, tmp); + std::size_t const offset = (n < total) ? total - n : 0; + auto bs = buffer_slice(bs0, offset); + check_eq(bs.data(), pat); + check_iterators(bs.data(), pat, tmp); } } } diff --git a/test/unit/buffers/buffer_array.cpp b/test/unit/detail/buffer_array.cpp similarity index 79% rename from test/unit/buffers/buffer_array.cpp rename to test/unit/detail/buffer_array.cpp index e3d7773c3..1b8091942 100644 --- a/test/unit/buffers/buffer_array.cpp +++ b/test/unit/detail/buffer_array.cpp @@ -8,9 +8,9 @@ // // Test that header file is self-contained. -#include +#include -#include "test_buffers.hpp" +#include "../buffers/test_buffers.hpp" #include #include @@ -19,29 +19,29 @@ namespace boost { namespace capy { -// std::ranges concepts for const_buffer_array +// std::ranges concepts for detail::const_buffer_array -static_assert(std::ranges::range>); -static_assert(std::ranges::input_range>); -static_assert(std::ranges::forward_range>); -static_assert(std::ranges::bidirectional_range>); -static_assert(std::ranges::random_access_range>); -static_assert(std::ranges::contiguous_range>); +static_assert(std::ranges::range>); +static_assert(std::ranges::input_range>); +static_assert(std::ranges::forward_range>); +static_assert(std::ranges::bidirectional_range>); +static_assert(std::ranges::random_access_range>); +static_assert(std::ranges::contiguous_range>); -static_assert(ConstBufferSequence>); -static_assert(!MutableBufferSequence>); +static_assert(ConstBufferSequence>); +static_assert(!MutableBufferSequence>); -// std::ranges concepts for mutable_buffer_array +// std::ranges concepts for detail::mutable_buffer_array -static_assert(std::ranges::range>); -static_assert(std::ranges::input_range>); -static_assert(std::ranges::forward_range>); -static_assert(std::ranges::bidirectional_range>); -static_assert(std::ranges::random_access_range>); -static_assert(std::ranges::contiguous_range>); +static_assert(std::ranges::range>); +static_assert(std::ranges::input_range>); +static_assert(std::ranges::forward_range>); +static_assert(std::ranges::bidirectional_range>); +static_assert(std::ranges::random_access_range>); +static_assert(std::ranges::contiguous_range>); -static_assert(ConstBufferSequence>); -static_assert(MutableBufferSequence>); +static_assert(ConstBufferSequence>); +static_assert(MutableBufferSequence>); struct buffer_array_test { @@ -52,7 +52,7 @@ struct buffer_array_test // default constructor { - const_buffer_array<4> ba; + detail::const_buffer_array<4> ba; BOOST_TEST_EQ(ba.to_span().size(), 0); BOOST_TEST_EQ(buffer_size(ba), 0); } @@ -60,7 +60,7 @@ struct buffer_array_test // single buffer constructor { const_buffer b(pat.data(), pat.size()); - const_buffer_array<4> ba(b); + detail::const_buffer_array<4> ba(b); BOOST_TEST_EQ(ba.to_span().size(), 1); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -69,7 +69,7 @@ struct buffer_array_test // empty buffer is skipped { const_buffer b(pat.data(), 0); - const_buffer_array<4> ba(b); + detail::const_buffer_array<4> ba(b); BOOST_TEST_EQ(ba.to_span().size(), 0); BOOST_TEST_EQ(buffer_size(ba), 0); } @@ -80,7 +80,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - const_buffer_array<4> ba(v); + detail::const_buffer_array<4> ba(v); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -89,8 +89,8 @@ struct buffer_array_test // copy constructor { const_buffer b(pat.data(), pat.size()); - const_buffer_array<4> ba1(b); - const_buffer_array<4> ba2(ba1); + detail::const_buffer_array<4> ba1(b); + detail::const_buffer_array<4> ba2(ba1); BOOST_TEST_EQ(ba2.to_span().size(), 1); BOOST_TEST_EQ(buffer_size(ba2), pat.size()); BOOST_TEST_EQ(test::make_string(ba2), pat); @@ -99,8 +99,8 @@ struct buffer_array_test // copy assignment { const_buffer b(pat.data(), pat.size()); - const_buffer_array<4> ba1(b); - const_buffer_array<4> ba2; + detail::const_buffer_array<4> ba1(b); + detail::const_buffer_array<4> ba2; ba2 = ba1; BOOST_TEST_EQ(ba2.to_span().size(), 1); BOOST_TEST_EQ(buffer_size(ba2), pat.size()); @@ -113,7 +113,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - const_buffer_array<2> ba; + detail::const_buffer_array<2> ba; ba = v; BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); @@ -122,7 +122,7 @@ struct buffer_array_test // span conversion { const_buffer b(pat.data(), pat.size()); - const_buffer_array<4> ba(b); + detail::const_buffer_array<4> ba(b); std::span sp = ba; BOOST_TEST_EQ(sp.size(), 1); BOOST_TEST_EQ(sp[0].data(), pat.data()); @@ -131,7 +131,7 @@ struct buffer_array_test // to_span { const_buffer b(pat.data(), pat.size()); - const_buffer_array<4> ba(b); + detail::const_buffer_array<4> ba(b); auto sp = ba.to_span(); BOOST_TEST_EQ(sp.size(), 1); BOOST_TEST_EQ(sp[0].data(), pat.data()); @@ -143,7 +143,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - const_buffer_array<2> ba(v); + detail::const_buffer_array<2> ba(v); BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); } @@ -157,7 +157,7 @@ struct buffer_array_test bool threw = false; try { - const_buffer_array<2> ba(std::in_place, v); + detail::const_buffer_array<2> ba(std::in_place, v); (void)ba; } catch(std::length_error const&) @@ -173,7 +173,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - const_buffer_array<4> ba(std::in_place, v); + detail::const_buffer_array<4> ba(std::in_place, v); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -185,7 +185,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - const_buffer_array<4> ba(v.begin(), v.end()); + detail::const_buffer_array<4> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -197,7 +197,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - const_buffer_array<2> ba(v.begin(), v.end()); + detail::const_buffer_array<2> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); } @@ -205,7 +205,7 @@ struct buffer_array_test // iterator-pair empty range { std::vector v; - const_buffer_array<4> ba(v.begin(), v.end()); + detail::const_buffer_array<4> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 0); BOOST_TEST_EQ(buffer_size(ba), 0); } @@ -217,7 +217,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 0); v.emplace_back(pat.data() + 3, 5); - const_buffer_array<4> ba(v.begin(), v.end()); + detail::const_buffer_array<4> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); } @@ -231,7 +231,7 @@ struct buffer_array_test bool threw = false; try { - const_buffer_array<2> ba( + detail::const_buffer_array<2> ba( std::in_place, v.begin(), v.end()); (void)ba; } @@ -248,7 +248,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - const_buffer_array<4> ba( + detail::const_buffer_array<4> ba( std::in_place, v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); @@ -262,7 +262,7 @@ struct buffer_array_test std::vector v; v.emplace_back(pat.data(), i); v.emplace_back(pat.data() + i, pat.size() - i); - const_buffer_array<4> ba(v); + detail::const_buffer_array<4> ba(v); test::check_sequence(ba, pat); } } @@ -275,7 +275,7 @@ struct buffer_array_test // default constructor { - mutable_buffer_array<4> ba; + detail::mutable_buffer_array<4> ba; BOOST_TEST_EQ(ba.to_span().size(), 0); BOOST_TEST_EQ(buffer_size(ba), 0); } @@ -283,7 +283,7 @@ struct buffer_array_test // single buffer constructor { mutable_buffer b(pat.data(), pat.size()); - mutable_buffer_array<4> ba(b); + detail::mutable_buffer_array<4> ba(b); BOOST_TEST_EQ(ba.to_span().size(), 1); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -295,7 +295,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - mutable_buffer_array<4> ba(v); + detail::mutable_buffer_array<4> ba(v); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -304,8 +304,8 @@ struct buffer_array_test // copy constructor { mutable_buffer b(pat.data(), pat.size()); - mutable_buffer_array<4> ba1(b); - mutable_buffer_array<4> ba2(ba1); + detail::mutable_buffer_array<4> ba1(b); + detail::mutable_buffer_array<4> ba2(ba1); BOOST_TEST_EQ(ba2.to_span().size(), 1); BOOST_TEST_EQ(buffer_size(ba2), pat.size()); BOOST_TEST_EQ(test::make_string(ba2), pat); @@ -314,8 +314,8 @@ struct buffer_array_test // copy assignment { mutable_buffer b(pat.data(), pat.size()); - mutable_buffer_array<4> ba1(b); - mutable_buffer_array<4> ba2; + detail::mutable_buffer_array<4> ba1(b); + detail::mutable_buffer_array<4> ba2; ba2 = ba1; BOOST_TEST_EQ(ba2.to_span().size(), 1); BOOST_TEST_EQ(buffer_size(ba2), pat.size()); @@ -328,7 +328,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - mutable_buffer_array<2> ba; + detail::mutable_buffer_array<2> ba; ba = v; BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); @@ -337,7 +337,7 @@ struct buffer_array_test // span conversion { mutable_buffer b(pat.data(), pat.size()); - mutable_buffer_array<4> ba(b); + detail::mutable_buffer_array<4> ba(b); std::span sp = ba; BOOST_TEST_EQ(sp.size(), 1); BOOST_TEST_EQ(sp[0].data(), pat.data()); @@ -346,7 +346,7 @@ struct buffer_array_test // to_span { mutable_buffer b(pat.data(), pat.size()); - mutable_buffer_array<4> ba(b); + detail::mutable_buffer_array<4> ba(b); auto sp = ba.to_span(); BOOST_TEST_EQ(sp.size(), 1); BOOST_TEST_EQ(sp[0].data(), pat.data()); @@ -358,7 +358,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - mutable_buffer_array<2> ba(v); + detail::mutable_buffer_array<2> ba(v); BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); } @@ -372,7 +372,7 @@ struct buffer_array_test bool threw = false; try { - mutable_buffer_array<2> ba(std::in_place, v); + detail::mutable_buffer_array<2> ba(std::in_place, v); (void)ba; } catch(std::length_error const&) @@ -388,7 +388,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - mutable_buffer_array<4> ba(std::in_place, v); + detail::mutable_buffer_array<4> ba(std::in_place, v); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -400,7 +400,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - mutable_buffer_array<4> ba(v.begin(), v.end()); + detail::mutable_buffer_array<4> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); BOOST_TEST_EQ(test::make_string(ba), pat); @@ -412,7 +412,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - mutable_buffer_array<2> ba(v.begin(), v.end()); + detail::mutable_buffer_array<2> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); } @@ -420,7 +420,7 @@ struct buffer_array_test // iterator-pair empty range { std::vector v; - mutable_buffer_array<4> ba(v.begin(), v.end()); + detail::mutable_buffer_array<4> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 0); BOOST_TEST_EQ(buffer_size(ba), 0); } @@ -432,7 +432,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 0); v.emplace_back(pat.data() + 3, 5); - mutable_buffer_array<4> ba(v.begin(), v.end()); + detail::mutable_buffer_array<4> ba(v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 2); BOOST_TEST_EQ(buffer_size(ba), 8); } @@ -446,7 +446,7 @@ struct buffer_array_test bool threw = false; try { - mutable_buffer_array<2> ba( + detail::mutable_buffer_array<2> ba( std::in_place, v.begin(), v.end()); (void)ba; } @@ -463,7 +463,7 @@ struct buffer_array_test v.emplace_back(pat.data(), 3); v.emplace_back(pat.data() + 3, 5); v.emplace_back(pat.data() + 8, pat.size() - 8); - mutable_buffer_array<4> ba( + detail::mutable_buffer_array<4> ba( std::in_place, v.begin(), v.end()); BOOST_TEST_EQ(ba.to_span().size(), 3); BOOST_TEST_EQ(buffer_size(ba), pat.size()); @@ -477,7 +477,7 @@ struct buffer_array_test std::vector v; v.emplace_back(pat.data(), i); v.emplace_back(pat.data() + i, pat.size() - i); - mutable_buffer_array<4> ba(v); + detail::mutable_buffer_array<4> ba(v); test::check_sequence(ba, pat); } } diff --git a/test/unit/io/write_now.cpp b/test/unit/io/write_now.cpp index a2285a5ff..eadb02385 100644 --- a/test/unit/io/write_now.cpp +++ b/test/unit/io/write_now.cpp @@ -10,7 +10,6 @@ // Test that header file is self-contained. #include -#include #include #include #include @@ -148,7 +147,7 @@ class write_now_test std::string s1("ab"); std::string s2("cdefgh"); - const_buffer_pair bp{{ + std::array bp{{ const_buffer(s1.data(), s1.size()), const_buffer(s2.data(), s2.size()) }}; diff --git a/test/unit/read.cpp b/test/unit/read.cpp index 206b16a38..c73ba4f1f 100644 --- a/test/unit/read.cpp +++ b/test/unit/read.cpp @@ -10,7 +10,6 @@ // Test that header file is self-contained. #include -#include #include #include #include @@ -108,7 +107,7 @@ struct buffer_pair_factory std::memset(storage2, 0, sizeof(storage2)); } - mutable_buffer_pair + std::array buffer() { return {{ diff --git a/test/unit/test/bufgrind.cpp b/test/unit/test/bufgrind.cpp index a3e647ba1..64ed44d43 100644 --- a/test/unit/test/bufgrind.cpp +++ b/test/unit/test/bufgrind.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -58,8 +59,8 @@ class bufgrind_test int count = 0; while(bg) { auto [b1, b2] = co_await bg.next(); - BOOST_TEST_EQ(buffer_size(b1), 0u); - BOOST_TEST_EQ(buffer_size(b2), 0u); + BOOST_TEST_EQ(buffer_size(b1.data()), 0u); + BOOST_TEST_EQ(buffer_size(b2.data()), 0u); ++count; } BOOST_TEST_EQ(count, 1); @@ -80,11 +81,11 @@ class bufgrind_test while(bg) { auto [b1, b2] = co_await bg.next(); if(count == 0) { - BOOST_TEST_EQ(buffer_size(b1), 0u); - BOOST_TEST_EQ(buffer_size(b2), 1u); + BOOST_TEST_EQ(buffer_size(b1.data()), 0u); + BOOST_TEST_EQ(buffer_size(b2.data()), 1u); } else if(count == 1) { - BOOST_TEST_EQ(buffer_size(b1), 1u); - BOOST_TEST_EQ(buffer_size(b2), 0u); + BOOST_TEST_EQ(buffer_size(b1.data()), 1u); + BOOST_TEST_EQ(buffer_size(b2.data()), 0u); } ++count; } @@ -107,9 +108,9 @@ class bufgrind_test while(bg) { auto [b1, b2] = co_await bg.next(); - BOOST_TEST_EQ(buffer_to_string(b1, b2), data); - BOOST_TEST_EQ(b1.size(), static_cast(count)); - BOOST_TEST_EQ(b2.size(), data.size() - count); + BOOST_TEST_EQ(buffer_to_string(b1.data(), b2.data()), data); + BOOST_TEST_EQ(buffer_size(b1.data()), static_cast(count)); + BOOST_TEST_EQ(buffer_size(b2.data()), data.size() - count); ++count; } BOOST_TEST_EQ(count, 6); @@ -129,7 +130,7 @@ class bufgrind_test std::vector positions; while(bg) { auto [b1, b2] = co_await bg.next(); - positions.push_back(buffer_size(b1)); + positions.push_back(buffer_size(b1.data())); } // Expect: 0, 3, 6, 9, 10 (always includes final position) @@ -156,7 +157,7 @@ class bufgrind_test std::vector positions; while(bg) { auto [b1, b2] = co_await bg.next(); - positions.push_back(buffer_size(b1)); + positions.push_back(buffer_size(b1.data())); } // Expect: 0, 2, 4, 6 @@ -200,7 +201,7 @@ class bufgrind_test std::vector positions; while(bg) { auto [b1, b2] = co_await bg.next(); - positions.push_back(buffer_size(b1)); + positions.push_back(buffer_size(b1.data())); } // Expect: 0, 3 (clamped to size, then final) @@ -224,11 +225,10 @@ class bufgrind_test while(bg) { auto [b1, b2] = co_await bg.next(); - // slice_type is mutable_buffer - // Verify sizes are correct and types are mutable - static_assert(std::is_same_v); - static_assert(std::is_same_v); - BOOST_TEST_EQ(b1.size() + b2.size(), 5u); + // Slices over a mutable input model MutableSlice + static_assert(MutableSlice); + static_assert(MutableSlice); + BOOST_TEST_EQ(buffer_size(b1.data()) + buffer_size(b2.data()), 5u); } }); BOOST_TEST(r.success); @@ -247,11 +247,13 @@ class bufgrind_test while(bg) { auto [b1, b2] = co_await bg.next(); - // slice_type is const_buffer - // Verify sizes are correct and types are const - static_assert(std::is_same_v); - static_assert(std::is_same_v); - BOOST_TEST_EQ(b1.size() + b2.size(), 5u); + // Slices over a const-only input model Slice but not + // MutableSlice. + static_assert(Slice); + static_assert(!MutableSlice); + static_assert(Slice); + static_assert(!MutableSlice); + BOOST_TEST_EQ(buffer_size(b1.data()) + buffer_size(b2.data()), 5u); } }); BOOST_TEST(r.success); @@ -276,7 +278,7 @@ class bufgrind_test auto [b1, b2] = co_await bg.next(); // Verify concatenation reconstructs original - BOOST_TEST_EQ(buffer_to_string(b1, b2), "abcdef"); + BOOST_TEST_EQ(buffer_to_string(b1.data(), b2.data()), "abcdef"); ++count; } BOOST_TEST_EQ(count, 7); @@ -299,17 +301,15 @@ class bufgrind_test // Set up read_stream with data matching b1 size read_stream rs(f); - rs.provide(std::string_view( - static_cast(b1.data()), - b1.size())); + rs.provide(buffer_to_string(b1.data())); // Read into a destination buffer - if(b1.size() > 0) { + if(buffer_size(b1.data()) > 0) { std::string dest; - dest.resize(b1.size()); + dest.resize(buffer_size(b1.data())); auto [ec, n] = co_await rs.read_some(make_buffer(dest)); BOOST_TEST(! ec); - BOOST_TEST_EQ(n, b1.size()); + BOOST_TEST_EQ(n, buffer_size(b1.data())); } } }); @@ -332,20 +332,20 @@ class bufgrind_test // Write b1 then b2 to stream write_stream ws(f); - if(b1.size() > 0) { - auto [ec1, n1] = co_await ws.write_some(b1); + if(buffer_size(b1.data()) > 0) { + auto [ec1, n1] = co_await ws.write_some(b1.data()); BOOST_TEST(! ec1); - BOOST_TEST_EQ(n1, b1.size()); + BOOST_TEST_EQ(n1, buffer_size(b1.data())); } - if(b2.size() > 0) { - auto [ec2, n2] = co_await ws.write_some(b2); + if(buffer_size(b2.data()) > 0) { + auto [ec2, n2] = co_await ws.write_some(b2.data()); BOOST_TEST(! ec2); - BOOST_TEST_EQ(n2, b2.size()); + BOOST_TEST_EQ(n2, buffer_size(b2.data())); } // Verify total written equals original - BOOST_TEST_EQ(ws.data(), buffer_to_string(b1, b2)); + BOOST_TEST_EQ(ws.data(), buffer_to_string(b1.data(), b2.data())); } }); BOOST_TEST(r.success); @@ -369,16 +369,16 @@ class bufgrind_test // Write both parts through stream write_stream ws(f); - if(b1.size() > 0) { - auto [ec, n] = co_await ws.write_some(b1); + if(buffer_size(b1.data()) > 0) { + auto [ec, n] = co_await ws.write_some(b1.data()); BOOST_TEST(! ec); } - if(b2.size() > 0) { - auto [ec, n] = co_await ws.write_some(b2); + if(buffer_size(b2.data()) > 0) { + auto [ec, n] = co_await ws.write_some(b2.data()); BOOST_TEST(! ec); } - BOOST_TEST_EQ(ws.data(), buffer_to_string(b1, b2)); + BOOST_TEST_EQ(ws.data(), buffer_to_string(b1.data(), b2.data())); BOOST_TEST_EQ(ws.data(), original); } } diff --git a/test/unit/test_dynamic_buffer.hpp b/test/unit/test_dynamic_buffer.hpp index c6779900e..79c58bea0 100644 --- a/test/unit/test_dynamic_buffer.hpp +++ b/test/unit/test_dynamic_buffer.hpp @@ -21,7 +21,6 @@ #include "test_suite.hpp" #include -#include namespace boost { namespace capy { @@ -51,25 +50,24 @@ grind_dynamic_buffer(F&& make_buffer_fn) while(bg) { auto [b1, b2] = co_await bg.next(); - BOOST_TEST_EQ(buffer_to_string(b1, b2), data); + BOOST_TEST_EQ(buffer_to_string(b1.data(), b2.data()), data); auto db = make_buffer_fn(); // Read b1 into dynamic buffer via read_stream read_stream rs(f); - rs.provide(std::string_view( - static_cast(b1.data()), b1.size())); + rs.provide(buffer_to_string(b1.data())); - if(buffer_size(b1) > 0) + if(buffer_size(b1.data()) > 0) { - auto mb = db.prepare(buffer_size(b1)); + auto mb = db.prepare(buffer_size(b1.data())); auto [ec, n] = co_await rs.read_some(mb); if(ec) co_return; db.commit(n); } - BOOST_TEST_EQ(db.size(), buffer_size(b1)); + BOOST_TEST_EQ(db.size(), buffer_size(b1.data())); // Write from dynamic buffer to write_stream write_stream ws(f); @@ -82,7 +80,7 @@ grind_dynamic_buffer(F&& make_buffer_fn) } // Verify round-trip - BOOST_TEST_EQ(ws.data(), buffer_to_string(b1)); + BOOST_TEST_EQ(ws.data(), buffer_to_string(b1.data())); db.consume(db.size()); BOOST_TEST_EQ(db.size(), 0u); diff --git a/test/unit/write.cpp b/test/unit/write.cpp index 54b56f9c9..3dcbe98a5 100644 --- a/test/unit/write.cpp +++ b/test/unit/write.cpp @@ -10,7 +10,6 @@ // Test that header file is self-contained. #include -#include #include #include #include @@ -97,7 +96,7 @@ struct buffer_pair_factory { } - const_buffer_pair + std::array buffer() const { return {{