100.00% Lines (65/65) 100.00% Functions (21/21)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_BUFFERS_HPP 10   #ifndef BOOST_CAPY_BUFFERS_HPP
11   #define BOOST_CAPY_BUFFERS_HPP 11   #define BOOST_CAPY_BUFFERS_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <concepts> 14   #include <concepts>
15   #include <cstddef> 15   #include <cstddef>
16   #include <iterator> 16   #include <iterator>
17   #include <memory> 17   #include <memory>
18   #include <ranges> 18   #include <ranges>
19   #include <type_traits> 19   #include <type_traits>
20   20  
21   // https://www.boost.org/doc/libs/1_65_0/doc/html/boost_asio/reference/ConstBufferSequence.html 21   // https://www.boost.org/doc/libs/1_65_0/doc/html/boost_asio/reference/ConstBufferSequence.html
22   22  
23   namespace boost { 23   namespace boost {
24   24  
25   namespace asio { 25   namespace asio {
26   class const_buffer; 26   class const_buffer;
27   class mutable_buffer; 27   class mutable_buffer;
28   } // asio 28   } // asio
29   29  
30   namespace capy { 30   namespace capy {
31   31  
32   class const_buffer; 32   class const_buffer;
33   class mutable_buffer; 33   class mutable_buffer;
34   34  
35   /** A reference to a contiguous region of writable memory. 35   /** A reference to a contiguous region of writable memory.
36   36  
37   Represents a pointer and size pair for a modifiable byte range. 37   Represents a pointer and size pair for a modifiable byte range.
38   Does not own the memory. Satisfies `MutableBufferSequence` (as a 38   Does not own the memory. Satisfies `MutableBufferSequence` (as a
39   single-element sequence) and is implicitly convertible to 39   single-element sequence) and is implicitly convertible to
40   `const_buffer`. 40   `const_buffer`.
41   41  
42   @see const_buffer, MutableBufferSequence 42   @see const_buffer, MutableBufferSequence
43   */ 43   */
44   class mutable_buffer 44   class mutable_buffer
45   { 45   {
46   unsigned char* p_ = nullptr; 46   unsigned char* p_ = nullptr;
47   std::size_t n_ = 0; 47   std::size_t n_ = 0;
48   48  
49   public: 49   public:
50   /// Construct an empty buffer. 50   /// Construct an empty buffer.
HITCBC 51   29 mutable_buffer() = default; 51   29 mutable_buffer() = default;
52   52  
53   /// Construct a copy. 53   /// Construct a copy.
54   mutable_buffer( 54   mutable_buffer(
55   mutable_buffer const&) = default; 55   mutable_buffer const&) = default;
56   56  
57   /// Assign by copying. 57   /// Assign by copying.
58   mutable_buffer& operator=( 58   mutable_buffer& operator=(
59   mutable_buffer const&) = default; 59   mutable_buffer const&) = default;
60   60  
61   /// Construct from pointer and size. 61   /// Construct from pointer and size.
HITCBC 62   42582 constexpr mutable_buffer( 62   42582 constexpr mutable_buffer(
63   void* data, std::size_t size) noexcept 63   void* data, std::size_t size) noexcept
HITCBC 64   42582 : p_(static_cast<unsigned char*>(data)) 64   42582 : p_(static_cast<unsigned char*>(data))
HITCBC 65   42582 , n_(size) 65   42582 , n_(size)
66   { 66   {
HITCBC 67   42582 } 67   42582 }
68   68  
69   /// Return a pointer to the memory region. 69   /// Return a pointer to the memory region.
HITCBC 70   62337 constexpr void* data() const noexcept 70   62337 constexpr void* data() const noexcept
71   { 71   {
HITCBC 72   62337 return p_; 72   62337 return p_;
73   } 73   }
74   74  
75   /// Return the size in bytes. 75   /// Return the size in bytes.
HITCBC 76   97996 constexpr std::size_t size() const noexcept 76   97996 constexpr std::size_t size() const noexcept
77   { 77   {
HITCBC 78   97996 return n_; 78   97996 return n_;
79   } 79   }
80   80  
81   /** Advance the buffer start, shrinking the region. 81   /** Advance the buffer start, shrinking the region.
82   82  
83   @param n Bytes to skip. Clamped to `size()`. 83   @param n Bytes to skip. Clamped to `size()`.
84   */ 84   */
85   mutable_buffer& 85   mutable_buffer&
HITCBC 86   19809 operator+=(std::size_t n) noexcept 86   19809 operator+=(std::size_t n) noexcept
87   { 87   {
HITCBC 88   19809 if( n > n_) 88   19809 if( n > n_)
HITCBC 89   1 n = n_; 89   1 n = n_;
HITCBC 90   19809 p_ += n; 90   19809 p_ += n;
HITCBC 91   19809 n_ -= n; 91   19809 n_ -= n;
HITCBC 92   19809 return *this; 92   19809 return *this;
93   } 93   }
94   }; 94   };
95   95  
96   /** A reference to a contiguous region of read-only memory. 96   /** A reference to a contiguous region of read-only memory.
97   97  
98   Represents a pointer and size pair for a non-modifiable byte range. 98   Represents a pointer and size pair for a non-modifiable byte range.
99   Does not own the memory. Satisfies `ConstBufferSequence` (as a 99   Does not own the memory. Satisfies `ConstBufferSequence` (as a
100   single-element sequence). Implicitly constructible from 100   single-element sequence). Implicitly constructible from
101   `mutable_buffer`. 101   `mutable_buffer`.
102   102  
103   @see mutable_buffer, ConstBufferSequence 103   @see mutable_buffer, ConstBufferSequence
104   */ 104   */
105   class const_buffer 105   class const_buffer
106   { 106   {
107   unsigned char const* p_ = nullptr; 107   unsigned char const* p_ = nullptr;
108   std::size_t n_ = 0; 108   std::size_t n_ = 0;
109   109  
110   public: 110   public:
111   /// Construct an empty buffer. 111   /// Construct an empty buffer.
HITCBC 112   57 const_buffer() = default; 112   57 const_buffer() = default;
113   113  
114   /// Construct a copy. 114   /// Construct a copy.
115   const_buffer(const_buffer const&) = default; 115   const_buffer(const_buffer const&) = default;
116   116  
117   /// Assign by copying. 117   /// Assign by copying.
118   const_buffer& operator=( 118   const_buffer& operator=(
119   const_buffer const& other) = default; 119   const_buffer const& other) = default;
120   120  
121   /// Construct from pointer and size. 121   /// Construct from pointer and size.
HITCBC 122   43427 constexpr const_buffer( 122   43427 constexpr const_buffer(
123   void const* data, std::size_t size) noexcept 123   void const* data, std::size_t size) noexcept
HITCBC 124   43427 : p_(static_cast<unsigned char const*>(data)) 124   43427 : p_(static_cast<unsigned char const*>(data))
HITCBC 125   43427 , n_(size) 125   43427 , n_(size)
126   { 126   {
HITCBC 127   43427 } 127   43427 }
128   128  
129   /// Construct from mutable_buffer. 129   /// Construct from mutable_buffer.
HITCBC 130   12005 constexpr const_buffer( 130   12005 constexpr const_buffer(
131   mutable_buffer const& b) noexcept 131   mutable_buffer const& b) noexcept
HITCBC 132   12005 : p_(static_cast<unsigned char const*>(b.data())) 132   12005 : p_(static_cast<unsigned char const*>(b.data()))
HITCBC 133   12005 , n_(b.size()) 133   12005 , n_(b.size())
134   { 134   {
HITCBC 135   12005 } 135   12005 }
136   136  
137   /// Return a pointer to the memory region. 137   /// Return a pointer to the memory region.
HITCBC 138   59971 constexpr void const* data() const noexcept 138   59971 constexpr void const* data() const noexcept
139   { 139   {
HITCBC 140   59971 return p_; 140   59971 return p_;
141   } 141   }
142   142  
143   /// Return the size in bytes. 143   /// Return the size in bytes.
HITCBC 144   112898 constexpr std::size_t size() const noexcept 144   112898 constexpr std::size_t size() const noexcept
145   { 145   {
HITCBC 146   112898 return n_; 146   112898 return n_;
147   } 147   }
148   148  
149   /** Advance the buffer start, shrinking the region. 149   /** Advance the buffer start, shrinking the region.
150   150  
151   @param n Bytes to skip. Clamped to `size()`. 151   @param n Bytes to skip. Clamped to `size()`.
152   */ 152   */
153   const_buffer& 153   const_buffer&
HITCBC 154   19810 operator+=(std::size_t n) noexcept 154   19810 operator+=(std::size_t n) noexcept
155   { 155   {
HITCBC 156   19810 if( n > n_) 156   19810 if( n > n_)
HITCBC 157   1 n = n_; 157   1 n = n_;
HITCBC 158   19810 p_ += n; 158   19810 p_ += n;
HITCBC 159   19810 n_ -= n; 159   19810 n_ -= n;
HITCBC 160   19810 return *this; 160   19810 return *this;
161   } 161   }
162   }; 162   };
163   163  
164   /** Concept for sequences of read-only buffer regions. 164   /** Concept for sequences of read-only buffer regions.
165   165  
166   A type satisfies `ConstBufferSequence` if it represents one or more 166   A type satisfies `ConstBufferSequence` if it represents one or more
167   contiguous memory regions that can be read. This includes single 167   contiguous memory regions that can be read. This includes single
168   buffers (convertible to `const_buffer`) and ranges of buffers. 168   buffers (convertible to `const_buffer`) and ranges of buffers.
169   169  
170   @par Syntactic Requirements 170   @par Syntactic Requirements
171   @li Convertible to `const_buffer`, OR 171   @li Convertible to `const_buffer`, OR
172   @li A bidirectional range with value type convertible to `const_buffer` 172   @li A bidirectional range with value type convertible to `const_buffer`
173   173  
174   @see const_buffer, MutableBufferSequence 174   @see const_buffer, MutableBufferSequence
175   */ 175   */
176   template<typename T> 176   template<typename T>
177   concept ConstBufferSequence = 177   concept ConstBufferSequence =
178   std::is_convertible_v<T, const_buffer> || ( 178   std::is_convertible_v<T, const_buffer> || (
179   std::ranges::bidirectional_range<T> && 179   std::ranges::bidirectional_range<T> &&
180   std::is_convertible_v<std::ranges::range_value_t<T>, const_buffer>); 180   std::is_convertible_v<std::ranges::range_value_t<T>, const_buffer>);
181   181  
182   /** Concept for sequences of writable buffer regions. 182   /** Concept for sequences of writable buffer regions.
183   183  
184   A type satisfies `MutableBufferSequence` if it represents one or more 184   A type satisfies `MutableBufferSequence` if it represents one or more
185   contiguous memory regions that can be written. This includes single 185   contiguous memory regions that can be written. This includes single
186   buffers (convertible to `mutable_buffer`) and ranges of buffers. 186   buffers (convertible to `mutable_buffer`) and ranges of buffers.
187   Every `MutableBufferSequence` also satisfies `ConstBufferSequence`. 187   Every `MutableBufferSequence` also satisfies `ConstBufferSequence`.
188   188  
189   @par Syntactic Requirements 189   @par Syntactic Requirements
190   @li Convertible to `mutable_buffer`, OR 190   @li Convertible to `mutable_buffer`, OR
191   @li A bidirectional range with value type convertible to `mutable_buffer` 191   @li A bidirectional range with value type convertible to `mutable_buffer`
192   192  
193   @see mutable_buffer, ConstBufferSequence 193   @see mutable_buffer, ConstBufferSequence
194   */ 194   */
195   template<typename T> 195   template<typename T>
196   concept MutableBufferSequence = 196   concept MutableBufferSequence =
197   std::is_convertible_v<T, mutable_buffer> || ( 197   std::is_convertible_v<T, mutable_buffer> || (
198   std::ranges::bidirectional_range<T> && 198   std::ranges::bidirectional_range<T> &&
199   std::is_convertible_v<std::ranges::range_value_t<T>, mutable_buffer>); 199   std::is_convertible_v<std::ranges::range_value_t<T>, mutable_buffer>);
200   200  
201 - /** Return an iterator to the first buffer in a sequence. 201 + namespace detail {
202 - 202 + struct begin_fn
203 - Handles single buffers and ranges uniformly. For a single buffer,  
204 - returns a pointer to it (forming a one-element range).  
205 - */  
206 - constexpr struct begin_mrdocs_workaround_t  
207   { 203   {
208   template<std::convertible_to<const_buffer> ConvertibleToBuffer> 204   template<std::convertible_to<const_buffer> ConvertibleToBuffer>
HITCBC 209   13217 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const* 205   13217 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
210   { 206   {
HITCBC 211   13217 return std::addressof(b); 207   13217 return std::addressof(b);
212   } 208   }
213   209  
214   template<ConstBufferSequence BS> 210   template<ConstBufferSequence BS>
215   requires (!std::convertible_to<BS, const_buffer>) 211   requires (!std::convertible_to<BS, const_buffer>)
HITCBC 216   46371 auto operator()(BS const& bs) const noexcept 212   46371 auto operator()(BS const& bs) const noexcept
217   { 213   {
HITCBC 218   46371 return std::ranges::begin(bs); 214   46371 return std::ranges::begin(bs);
219   } 215   }
220   216  
221   template<ConstBufferSequence BS> 217   template<ConstBufferSequence BS>
222   requires (!std::convertible_to<BS, const_buffer>) 218   requires (!std::convertible_to<BS, const_buffer>)
HITCBC 223   9192 auto operator()(BS& bs) const noexcept 219   9192 auto operator()(BS& bs) const noexcept
224   { 220   {
HITCBC 225   9192 return std::ranges::begin(bs); 221   9192 return std::ranges::begin(bs);
226   } 222   }
227 - } begin {}; 223 + };
  224 + } // detail
228   225  
229 - /** Return an iterator past the last buffer in a sequence. 226 + /** Return an iterator to the first buffer in a sequence.
230   227  
231   Handles single buffers and ranges uniformly. For a single buffer, 228   Handles single buffers and ranges uniformly. For a single buffer,
232 - returns a pointer one past it. 229 + returns a pointer to it (forming a one-element range).
  230 +
  231 + @param bs The buffer sequence.
  232 +
  233 + @return An iterator to the first buffer in the sequence.
233   */ 234   */
234 - constexpr struct end_mrdocs_workaround_t 235 + constexpr detail::begin_fn begin {};
  236 +
  237 + namespace detail {
  238 + struct end_fn
235   { 239   {
236   template<std::convertible_to<const_buffer> ConvertibleToBuffer> 240   template<std::convertible_to<const_buffer> ConvertibleToBuffer>
HITCBC 237   12955 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const* 241   12955 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
238   { 242   {
HITCBC 239   12955 return std::addressof(b) + 1; 243   12955 return std::addressof(b) + 1;
240   } 244   }
241   245  
242   template<ConstBufferSequence BS> 246   template<ConstBufferSequence BS>
243   requires (!std::convertible_to<BS, const_buffer>) 247   requires (!std::convertible_to<BS, const_buffer>)
HITCBC 244   46371 auto operator()(BS const& bs) const noexcept 248   46371 auto operator()(BS const& bs) const noexcept
245   { 249   {
HITCBC 246   46371 return std::ranges::end(bs); 250   46371 return std::ranges::end(bs);
247   } 251   }
248   252  
249   template<ConstBufferSequence BS> 253   template<ConstBufferSequence BS>
250   requires (!std::convertible_to<BS, const_buffer>) 254   requires (!std::convertible_to<BS, const_buffer>)
HITCBC 251   9192 auto operator()(BS& bs) const noexcept 255   9192 auto operator()(BS& bs) const noexcept
252   { 256   {
HITCBC 253   9192 return std::ranges::end(bs); 257   9192 return std::ranges::end(bs);
254   } 258   }
255 - } end {}; 259 + };
  260 + } // detail
256   261  
257 - /** Return the total byte count across all buffers in a sequence. 262 + /** Return an iterator past the last buffer in a sequence.
258   263  
259 - Sums the `size()` of each buffer in the sequence. This differs 264 + Handles single buffers and ranges uniformly. For a single buffer,
260 - from `buffer_length` which counts the number of buffer elements. 265 + returns a pointer one past it.
261   266  
262 - @par Example 267 + @param bs The buffer sequence.
263 - @code 268 +
264 - std::array<mutable_buffer, 2> bufs = { ... }; 269 + @return An iterator one past the last buffer in the sequence.
265 - std::size_t total = buffer_size( bufs ); // sum of both sizes  
266 - @endcode  
267   */ 270   */
268 - constexpr struct buffer_size_mrdocs_workaround_t 271 + constexpr detail::end_fn end {};
  272 +
  273 + namespace detail {
  274 + struct buffer_size_fn
269   { 275   {
270   // GCC 13 falsely flags reads of arr_[i].n_ in detail::buffer_array 276   // GCC 13 falsely flags reads of arr_[i].n_ in detail::buffer_array
271   // when iterating here. The class uses union storage with placement 277   // when iterating here. The class uses union storage with placement
272   // new for slots 0..n_-1, so reads inside this bounded loop are 278   // new for slots 0..n_-1, so reads inside this bounded loop are
273   // well-defined, but the optimizer can't prove the loop bound and 279   // well-defined, but the optimizer can't prove the loop bound and
274   // warns. The runtime cost of value-initializing all N slots is 280   // warns. The runtime cost of value-initializing all N slots is
275   // non-trivial for non-trivial value types, so we suppress instead. 281   // non-trivial for non-trivial value types, so we suppress instead.
276   #if defined(__GNUC__) && !defined(__clang__) 282   #if defined(__GNUC__) && !defined(__clang__)
277   #pragma GCC diagnostic push 283   #pragma GCC diagnostic push
278   #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 284   #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
279   #endif 285   #endif
280   template<ConstBufferSequence CB> 286   template<ConstBufferSequence CB>
HITCBC 281   12824 constexpr std::size_t operator()( 287   12824 constexpr std::size_t operator()(
282   CB const& bs) const noexcept 288   CB const& bs) const noexcept
283   { 289   {
HITCBC 284   12824 std::size_t n = 0; 290   12824 std::size_t n = 0;
HITCBC 285   12824 auto const e = capy::end(bs); 291   12824 auto const e = capy::end(bs);
HITCBC 286   27175 for(auto it = capy::begin(bs); it != e; ++it) 292   27175 for(auto it = capy::begin(bs); it != e; ++it)
HITCBC 287   14351 n += const_buffer(*it).size(); 293   14351 n += const_buffer(*it).size();
HITCBC 288   12824 return n; 294   12824 return n;
289   } 295   }
290   #if defined(__GNUC__) && !defined(__clang__) 296   #if defined(__GNUC__) && !defined(__clang__)
291   #pragma GCC diagnostic pop 297   #pragma GCC diagnostic pop
292   #endif 298   #endif
293 - } buffer_size {}; 299 + };
  300 + } // detail
294   301  
295 - /** Check if a buffer sequence contains no data. 302 + /** Return the total byte count across all buffers in a sequence.
296   303  
297 - @return `true` if all buffers have size zero or the sequence 304 + Sums the `size()` of each buffer in the sequence. This differs
298 - is empty. 305 + from `buffer_length` which counts the number of buffer elements.
  306 +
  307 + @param bs The buffer sequence.
  308 +
  309 + @return The total number of bytes across all buffers in the sequence.
  310 +
  311 + @par Example
  312 + @code
  313 + std::array<mutable_buffer, 2> bufs = { ... };
  314 + std::size_t total = buffer_size( bufs ); // sum of both sizes
  315 + @endcode
299   */ 316   */
300 - constexpr struct buffer_empty_mrdocs_workaround_t 317 + constexpr detail::buffer_size_fn buffer_size {};
  318 +
  319 + namespace detail {
  320 + struct buffer_empty_fn
301   { 321   {
302   // See note on buffer_size above — same union-storage false positive. 322   // See note on buffer_size above — same union-storage false positive.
303   #if defined(__GNUC__) && !defined(__clang__) 323   #if defined(__GNUC__) && !defined(__clang__)
304   #pragma GCC diagnostic push 324   #pragma GCC diagnostic push
305   #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 325   #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
306   #endif 326   #endif
307   template<ConstBufferSequence CB> 327   template<ConstBufferSequence CB>
HITCBC 308   4263 constexpr bool operator()( 328   4263 constexpr bool operator()(
309   CB const& bs) const noexcept 329   CB const& bs) const noexcept
310   { 330   {
HITCBC 311   4263 auto it = begin(bs); 331   4263 auto it = begin(bs);
HITCBC 312   4263 auto const end_ = end(bs); 332   4263 auto const end_ = end(bs);
HITCBC 313   4304 while(it != end_) 333   4304 while(it != end_)
314   { 334   {
HITCBC 315   4264 const_buffer b(*it++); 335   4264 const_buffer b(*it++);
HITCBC 316   4264 if(b.size() != 0) 336   4264 if(b.size() != 0)
HITCBC 317   4223 return false; 337   4223 return false;
318   } 338   }
HITCBC 319   40 return true; 339   40 return true;
320   } 340   }
321   #if defined(__GNUC__) && !defined(__clang__) 341   #if defined(__GNUC__) && !defined(__clang__)
322   #pragma GCC diagnostic pop 342   #pragma GCC diagnostic pop
323   #endif 343   #endif
324 - } buffer_empty {}; 344 + };
  345 + } // detail
  346 +
  347 + /** Check if a buffer sequence contains no data.
  348 +
  349 + @param bs The buffer sequence.
  350 +
  351 + @return `true` if all buffers have size zero or the sequence
  352 + is empty.
  353 + */
  354 + constexpr detail::buffer_empty_fn buffer_empty {};
325   355  
326   namespace detail { 356   namespace detail {
327   357  
328   template<class It> 358   template<class It>
329   auto 359   auto
HITCBC 330   263 length_impl(It first, It last, int) 360   263 length_impl(It first, It last, int)
331   -> decltype(static_cast<std::size_t>(last - first)) 361   -> decltype(static_cast<std::size_t>(last - first))
332   { 362   {
HITCBC 333   263 return static_cast<std::size_t>(last - first); 363   263 return static_cast<std::size_t>(last - first);
334   } 364   }
335   365  
336   template<class It> 366   template<class It>
337   std::size_t 367   std::size_t
338   length_impl(It first, It last, long) 368   length_impl(It first, It last, long)
339   { 369   {
340   std::size_t n = 0; 370   std::size_t n = 0;
341   while(first != last) 371   while(first != last)
342   { 372   {
343   ++first; 373   ++first;
344   ++n; 374   ++n;
345   } 375   }
346   return n; 376   return n;
347   } 377   }
348   378  
349   } // detail 379   } // detail
350   380  
351   /** Return the number of buffer elements in a sequence. 381   /** Return the number of buffer elements in a sequence.
352   382  
353   Counts the number of individual buffer objects, not bytes. 383   Counts the number of individual buffer objects, not bytes.
354   For a single buffer, returns 1. For a range, returns the 384   For a single buffer, returns 1. For a range, returns the
355   distance from `begin` to `end`. 385   distance from `begin` to `end`.
356   386  
357   @see buffer_size 387   @see buffer_size
358   */ 388   */
359   template<ConstBufferSequence CB> 389   template<ConstBufferSequence CB>
360   std::size_t 390   std::size_t
HITCBC 361   263 buffer_length(CB const& bs) 391   263 buffer_length(CB const& bs)
362   { 392   {
HITCBC 363   263 return detail::length_impl( 393   263 return detail::length_impl(
HITCBC 364   263 begin(bs), end(bs), 0); 394   263 begin(bs), end(bs), 0);
365   } 395   }
366   396  
367   /// Alias for `mutable_buffer` or `const_buffer` based on sequence type. 397   /// Alias for `mutable_buffer` or `const_buffer` based on sequence type.
368   template<typename BS> 398   template<typename BS>
369   using buffer_type = std::conditional_t< 399   using buffer_type = std::conditional_t<
370   MutableBufferSequence<BS>, 400   MutableBufferSequence<BS>,
371   mutable_buffer, const_buffer>; 401   mutable_buffer, const_buffer>;
372   402  
373   } // capy 403   } // capy
374   } // boost 404   } // boost
375   405  
376   #endif 406   #endif