libs/capy/include/boost/capy/buffers.hpp

100.0% Lines (93/93) 100.0% Functions (124/124) 90.5% Branches (19/21)
libs/capy/include/boost/capy/buffers.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
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)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_BUFFERS_HPP
11 #define BOOST_CAPY_BUFFERS_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <concepts>
15 #include <cstddef>
16 #include <iterator>
17 #include <memory>
18 #include <ranges>
19 #include <type_traits>
20
21 // https://www.boost.org/doc/libs/1_65_0/doc/html/boost_asio/reference/ConstBufferSequence.html
22
23 namespace boost {
24
25 namespace asio {
26 class const_buffer;
27 class mutable_buffer;
28 } // asio
29
30 namespace capy {
31
32 class const_buffer;
33 class mutable_buffer;
34
35 namespace detail {
36
37 // satisfies Asio's buffer constructors, CANNOT be removed!
38 template<class T, std::size_t Extent = (std::size_t)(-1)>
39 class basic_buffer
40 {
41 constexpr auto data() const noexcept ->
42 std::conditional_t<std::is_const_v<T>, void const*, void*>
43 {
44 return p_;
45 }
46
47 constexpr std::size_t size() const noexcept
48 {
49 return n_;
50 }
51
52 friend class capy::const_buffer;
53 friend class capy::mutable_buffer;
54 friend class asio::const_buffer;
55 friend class asio::mutable_buffer;
56 1165 basic_buffer() = default;
57 55073 constexpr basic_buffer(T* p, std::size_t n) noexcept : p_(p), n_(n) {}
58 constexpr basic_buffer<T, (std::size_t)(-1)> subspan(
59 std::size_t, std::size_t = (std::size_t)(-1)) const noexcept;
60
61 T* p_ = nullptr;
62 std::size_t n_ = 0;
63 };
64
65 } // detail
66
67 //------------------------------------------------
68
69 /** size tag for `tag_invoke`
70
71 This type is used in overloads of `tag_invoke`
72 for user-defined types to customize the `size()`
73 algorithm.
74 */
75 struct size_tag {};
76
77 /** slice tag for `tag_invoke`
78
79 This type is used in overloads of `tag_invoke`
80 for user-defined types to customize the slicing
81 algorithms.
82 */
83 struct slice_tag {};
84
85 /** slice constants for slice customization
86
87 This defines the possible values passed to
88 overloads of `tag_invoke` for user-defined
89 types which customize the slicing algorithms.
90 */
91 enum class slice_how
92 {
93 /// Indicates that the front of the buffer sequence should be trimmed
94 remove_prefix,
95
96 /// Indicates that the front of the buffer sequence should be preserved
97 keep_prefix
98 };
99
100 //------------------------------------------------
101
102 /** Holds a contiguous range of modifiable bytes
103 */
104 class mutable_buffer
105 : public detail::basic_buffer<unsigned char>
106 {
107 public:
108 /** Constructor.
109 */
110 580 mutable_buffer() = default;
111
112 /** Constructor.
113 */
114 mutable_buffer(
115 mutable_buffer const&) = default;
116
117 /** Assignment.
118 */
119 mutable_buffer& operator=(
120 mutable_buffer const&) = default;
121
122 /** Constructor.
123 */
124 23290 constexpr mutable_buffer(
125 void* data, std::size_t size) noexcept
126 23290 : basic_buffer<unsigned char>(
127 23290 static_cast<unsigned char*>(data), size)
128 {
129 23290 }
130
131 /** Constructor
132 */
133 template<class MutableBuffer>
134 requires std::same_as<MutableBuffer, asio::mutable_buffer>
135 constexpr mutable_buffer(
136 MutableBuffer const& b) noexcept
137 : basic_buffer<unsigned char>(
138 static_cast<unsigned char*>(
139 b.data()), b.size())
140 {
141 }
142
143 /** Return a pointer to the beginning of the memory region
144 */
145 49987 constexpr void* data() const noexcept
146 {
147 49987 return p_;
148 }
149
150 /** Return the number of valid bytes in the referenced memory region
151 */
152 81773 constexpr std::size_t size() const noexcept
153 {
154 81773 return n_;
155 }
156
157 /** Remove a prefix of the memory region
158
159 If the requested number of bytes is larger than the current size,
160 the resulting buffer will have size 0.
161
162 @param n The number of bytes to remove.
163 */
164 mutable_buffer&
165 22268 operator+=(std::size_t n) noexcept
166 {
167
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 22251 times.
22268 if( n > n_)
168 17 n = n_;
169 22268 p_ += n;
170 22268 n_ -= n;
171 22268 return *this;
172 }
173
174 /** Remove a slice from the buffer
175 */
176 friend
177 void
178 1335 tag_invoke(
179 slice_tag const&,
180 mutable_buffer& b,
181 slice_how how,
182 std::size_t n) noexcept
183 {
184 1335 b.do_slice(how, n);
185 1335 }
186
187 private:
188 1335 void do_slice(
189 slice_how how, std::size_t n) noexcept
190 {
191
2/3
✓ Branch 0 taken 659 times.
✓ Branch 1 taken 676 times.
✗ Branch 2 not taken.
1335 switch(how)
192 {
193 659 case slice_how::remove_prefix:
194 659 *this += n;
195 659 return;
196
197 676 case slice_how::keep_prefix:
198
2/2
✓ Branch 0 taken 584 times.
✓ Branch 1 taken 92 times.
676 if( n < n_)
199 584 n_ = n;
200 676 return;
201 }
202 }
203 };
204
205 //------------------------------------------------
206
207 /** Holds a contiguous range of unmodifiable bytes
208 */
209 class const_buffer
210 : public detail::basic_buffer<unsigned char const>
211 {
212 public:
213 /** Constructor
214 */
215 585 const_buffer() = default;
216
217 /** Constructor
218 */
219 const_buffer(const_buffer const&) = default;
220
221 /** Assignment
222
223 @par Postconditions
224 @code
225 this->data() == other.data() && this->size() == other.size()
226 @endcode
227 */
228 const_buffer& operator=(
229 const_buffer const& other) = default;
230
231 /** Constructor
232 */
233 20427 constexpr const_buffer(
234 void const* data, std::size_t size) noexcept
235 20427 : basic_buffer<unsigned char const>(
236 20427 static_cast<unsigned char const*>(data), size)
237 {
238 20427 }
239
240 /** Constructor
241 */
242 11356 constexpr const_buffer(
243 mutable_buffer const& b) noexcept
244 11356 : basic_buffer<unsigned char const>(
245 11356 static_cast<unsigned char const*>(b.data()), b.size())
246 {
247 11356 }
248
249 /** Constructor
250 */
251 template<class ConstBuffer>
252 requires (std::same_as<ConstBuffer, asio::const_buffer> ||
253 std::same_as<ConstBuffer, asio::mutable_buffer>)
254 constexpr const_buffer(
255 ConstBuffer const& b) noexcept
256 : basic_buffer<unsigned char const>(
257 static_cast<unsigned char const*>(
258 b.data()), b.size())
259 {
260 }
261
262 /** Return a pointer to the beginning of the memory region
263 */
264 47516 constexpr void const* data() const noexcept
265 {
266 47516 return p_;
267 }
268
269 /** Return the number of valid bytes in the referenced memory region
270 */
271 96037 constexpr std::size_t size() const noexcept
272 {
273 96037 return n_;
274 }
275
276 /** Remove a prefix of the memory region
277
278 If the requested number of bytes is larger than the current size,
279 the resulting buffer will have size 0.
280
281 @param n The number of bytes to remove.
282 */
283 const_buffer&
284 22931 operator+=(std::size_t n) noexcept
285 {
286
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 22915 times.
22931 if( n > n_)
287 16 n = n_;
288 22931 p_ += n;
289 22931 n_ -= n;
290 22931 return *this;
291 }
292
293 /** Remove a slice from the buffer
294 */
295 friend
296 void
297 2640 tag_invoke(
298 slice_tag const&,
299 const_buffer& b,
300 slice_how how,
301 std::size_t n) noexcept
302 {
303 2640 b.do_slice(how, n);
304 2640 }
305
306 private:
307 2640 void do_slice(
308 slice_how how, std::size_t n) noexcept
309 {
310
2/3
✓ Branch 0 taken 1313 times.
✓ Branch 1 taken 1327 times.
✗ Branch 2 not taken.
2640 switch(how)
311 {
312 1313 case slice_how::remove_prefix:
313 1313 *this += n;
314 1313 return;
315
316 1327 case slice_how::keep_prefix:
317
2/2
✓ Branch 0 taken 1238 times.
✓ Branch 1 taken 89 times.
1327 if( n < n_)
318 1238 n_ = n;
319 1327 return;
320 }
321 }
322 };
323
324 //------------------------------------------------
325
326 /** Concept for types that model ConstBufferSequence.
327
328 A type satisfies `ConstBufferSequence` if it is convertible
329 to `const_buffer`, or if it is a bidirectional range whose
330 value type is convertible to `const_buffer`.
331 */
332 template<typename T>
333 concept ConstBufferSequence =
334 std::is_convertible_v<T, const_buffer> || (
335 std::ranges::bidirectional_range<T> &&
336 std::is_convertible_v<std::ranges::range_value_t<T>, const_buffer>);
337
338 /** Concept for types that model MutableBufferSequence.
339
340 A type satisfies `MutableBufferSequence` if it is convertible
341 to `mutable_buffer`, or if it is a bidirectional range whose
342 value type is convertible to `mutable_buffer`.
343 */
344 template<typename T>
345 concept MutableBufferSequence =
346 std::is_convertible_v<T, mutable_buffer> || (
347 std::ranges::bidirectional_range<T> &&
348 std::is_convertible_v<std::ranges::range_value_t<T>, mutable_buffer>);
349
350 //------------------------------------------------------------------------------
351
352 /** Return an iterator pointing to the first element of a buffer sequence
353
354 This function returns an iterator to the beginning of the range denoted by
355 `t`. It handles both ranges and single buffers uniformly.
356
357 @par Constraints
358 @code
359 const_buffer_sequence<T>
360 @endcode
361
362 @param t The buffer sequence
363 */
364 constexpr struct begin_mrdocs_workaround_t
365 {
366 template<std::convertible_to<const_buffer> ConvertibleToBuffer>
367 15676 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
368 {
369 15676 return std::addressof(b);
370 }
371
372 template<ConstBufferSequence BS>
373 requires (!std::convertible_to<BS, const_buffer>)
374 42737 auto operator()(BS const& bs) const noexcept
375 {
376 42737 return std::ranges::begin(bs);
377 }
378
379 template<ConstBufferSequence BS>
380 requires (!std::convertible_to<BS, const_buffer>)
381 9742 auto operator()(BS& bs) const noexcept
382 {
383 9742 return std::ranges::begin(bs);
384 }
385 } begin {};
386
387 //------------------------------------------------------------------------------
388
389 /** Return an iterator to the end of the buffer sequence
390
391 This function returns an iterator to the end of the range denoted by
392 `t`. It handles both ranges and single buffers uniformly.
393
394 @par Constraints
395 @code
396 const_buffer_sequence<T>
397 @endcode
398
399 @param t The buffer sequence
400 */
401 constexpr struct end_mrdocs_workaround_t
402 {
403 template<std::convertible_to<const_buffer> ConvertibleToBuffer>
404 15436 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
405 {
406 15436 return std::addressof(b) + 1;
407 }
408
409 template<ConstBufferSequence BS>
410 requires (!std::convertible_to<BS, const_buffer>)
411 35208 auto operator()(BS const& bs) const noexcept
412 {
413 35208 return std::ranges::end(bs);
414 }
415
416 template<ConstBufferSequence BS>
417 requires (!std::convertible_to<BS, const_buffer>)
418 9742 auto operator()(BS& bs) const noexcept
419 {
420 9742 return std::ranges::end(bs);
421 }
422 } end {};
423
424 //------------------------------------------------------------------------------
425
426 template<ConstBufferSequence CB>
427 std::size_t
428 13020 tag_invoke(
429 size_tag const&,
430 CB const& bs) noexcept
431 {
432 13020 std::size_t n = 0;
433 13020 auto const e = end(bs);
434
3/3
✓ Branch 1 taken 17184 times.
✓ Branch 2 taken 14077 times.
✓ Branch 3 taken 1586 times.
32847 for(auto it = begin(bs); it != e; ++it)
435 19827 n += const_buffer(*it).size();
436 13020 return n;
437 }
438
439 //------------------------------------------------------------------------------
440
441 /** Return the total number of bytes in a buffer sequence
442
443 This function returns the sum of the number of bytes in each contiguous
444 buffer contained in the range or value. This is different from the length
445 of the sequence returned by `std::ranges::size(t)`
446
447 @par Constraints
448 @code
449 ConstBufferSequence<T>
450 @endcode
451
452 @par Example
453 @code
454 template<ConstBufferSequence CB>
455 bool is_small( CB const& bs ) noexcept
456 {
457 return buffer_size(bs) < 100;
458 }
459 @endcode
460 */
461 constexpr struct buffer_size_mrdocs_workaround_t
462 {
463 template<ConstBufferSequence CB>
464 13020 constexpr std::size_t operator()(
465 CB const& bs) const noexcept
466 {
467 13020 return tag_invoke(size_tag{}, bs);
468 }
469 } buffer_size {};
470
471 //-----------------------------------------------
472
473 /** Check if a buffer sequence contains no data.
474
475 A buffer sequence is considered empty if all of its
476 constituent buffers have size zero, or if the sequence
477 contains no buffers.
478
479 @param bs The buffer sequence to check.
480
481 @return `true` if the total size of all buffers is zero.
482 */
483 constexpr struct buffer_empty_mrdocs_workaround_t
484 {
485 template<ConstBufferSequence CB>
486 20 constexpr bool operator()(
487 CB const& bs) const noexcept
488 {
489 20 auto it = begin(bs);
490 20 auto const end_ = end(bs);
491
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
33 while(it != end_)
492 {
493 24 const_buffer b(*it++);
494
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
24 if(b.size() != 0)
495 11 return false;
496 }
497 9 return true;
498 }
499 } buffer_empty {};
500
501 //-----------------------------------------------
502
503 namespace detail {
504
505 template<class It>
506 auto
507 240 length_impl(It first, It last, int)
508 -> decltype(static_cast<std::size_t>(last - first))
509 {
510 240 return static_cast<std::size_t>(last - first);
511 }
512
513 template<class It>
514 std::size_t
515 length_impl(It first, It last, long)
516 {
517 std::size_t n = 0;
518 while(first != last)
519 {
520 ++first;
521 ++n;
522 }
523 return n;
524 }
525
526 } // detail
527
528 /** Return the number of elements in a buffer sequence.
529 */
530 template<ConstBufferSequence CB>
531 std::size_t
532 240 buffer_length(CB const& bs)
533 {
534 240 return detail::length_impl(
535 240 begin(bs), end(bs), 0);
536 }
537
538 /** Alias for const_buffer or mutable_buffer depending on sequence type.
539 */
540 template<typename BS>
541 using buffer_type = std::conditional_t<
542 MutableBufferSequence<BS>,
543 mutable_buffer, const_buffer>;
544
545 } // capy
546 } // boost
547
548 #endif
549