include/boost/capy/buffers.hpp

100.0% Lines (91/91) 100.0% Functions (140/140)
include/boost/capy/buffers.hpp
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
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 //------------------------------------------------
36
37 /// Tag type for customizing `buffer_size` via `tag_invoke`.
38 struct size_tag {};
39
40 /// Tag type for customizing slice operations via `tag_invoke`.
41 struct slice_tag {};
42
43 /** Constants for slice customization.
44
45 Passed to `tag_invoke` overloads to specify which portion
46 of a buffer sequence to retain.
47 */
48 enum class slice_how
49 {
50 /// Remove bytes from the front of the sequence.
51 remove_prefix,
52
53 /// Keep only the first N bytes.
54 keep_prefix
55 };
56
57 //------------------------------------------------
58
59 /** A reference to a contiguous region of writable memory.
60
61 Represents a pointer and size pair for a modifiable byte range.
62 Does not own the memory. Satisfies `MutableBufferSequence` (as a
63 single-element sequence) and is implicitly convertible to
64 `const_buffer`.
65
66 @see const_buffer, MutableBufferSequence
67 */
68 class mutable_buffer
69 {
70 unsigned char* p_ = nullptr;
71 std::size_t n_ = 0;
72
73 public:
74 /// Construct an empty buffer.
75 603 mutable_buffer() = default;
76
77 /// Copy constructor.
78 mutable_buffer(
79 mutable_buffer const&) = default;
80
81 /// Copy assignment.
82 mutable_buffer& operator=(
83 mutable_buffer const&) = default;
84
85 /// Construct from pointer and size.
86 27752 constexpr mutable_buffer(
87 void* data, std::size_t size) noexcept
88 27752 : p_(static_cast<unsigned char*>(data))
89 27752 , n_(size)
90 {
91 27752 }
92
93 /// Return a pointer to the memory region.
94 71546 constexpr void* data() const noexcept
95 {
96 71546 return p_;
97 }
98
99 /// Return the size in bytes.
100 109881 constexpr std::size_t size() const noexcept
101 {
102 109881 return n_;
103 }
104
105 /** Advance the buffer start, shrinking the region.
106
107 @param n Bytes to skip. Clamped to `size()`.
108 */
109 mutable_buffer&
110 26482 operator+=(std::size_t n) noexcept
111 {
112 26482 if( n > n_)
113 17 n = n_;
114 26482 p_ += n;
115 26482 n_ -= n;
116 26482 return *this;
117 }
118
119 /// Slice customization point for `tag_invoke`.
120 friend
121 void
122 1335 tag_invoke(
123 slice_tag const&,
124 mutable_buffer& b,
125 slice_how how,
126 std::size_t n) noexcept
127 {
128 1335 b.do_slice(how, n);
129 1335 }
130
131 private:
132 1335 void do_slice(
133 slice_how how, std::size_t n) noexcept
134 {
135 1335 switch(how)
136 {
137 659 case slice_how::remove_prefix:
138 659 *this += n;
139 659 return;
140
141 676 case slice_how::keep_prefix:
142 676 if( n < n_)
143 584 n_ = n;
144 676 return;
145 }
146 }
147 };
148
149 //------------------------------------------------
150
151 /** A reference to a contiguous region of read-only memory.
152
153 Represents a pointer and size pair for a non-modifiable byte range.
154 Does not own the memory. Satisfies `ConstBufferSequence` (as a
155 single-element sequence). Implicitly constructible from
156 `mutable_buffer`.
157
158 @see mutable_buffer, ConstBufferSequence
159 */
160 class const_buffer
161 {
162 unsigned char const* p_ = nullptr;
163 std::size_t n_ = 0;
164
165 public:
166 /// Construct an empty buffer.
167 631 const_buffer() = default;
168
169 /// Copy constructor.
170 const_buffer(const_buffer const&) = default;
171
172 /// Copy assignment.
173 const_buffer& operator=(
174 const_buffer const& other) = default;
175
176 /// Construct from pointer and size.
177 22294 constexpr const_buffer(
178 void const* data, std::size_t size) noexcept
179 22294 : p_(static_cast<unsigned char const*>(data))
180 22294 , n_(size)
181 {
182 22294 }
183
184 /// Construct from mutable_buffer.
185 16679 constexpr const_buffer(
186 mutable_buffer const& b) noexcept
187 16679 : p_(static_cast<unsigned char const*>(b.data()))
188 16679 , n_(b.size())
189 {
190 16679 }
191
192 /// Return a pointer to the memory region.
193 63750 constexpr void const* data() const noexcept
194 {
195 63750 return p_;
196 }
197
198 /// Return the size in bytes.
199 123901 constexpr std::size_t size() const noexcept
200 {
201 123901 return n_;
202 }
203
204 /** Advance the buffer start, shrinking the region.
205
206 @param n Bytes to skip. Clamped to `size()`.
207 */
208 const_buffer&
209 27137 operator+=(std::size_t n) noexcept
210 {
211 27137 if( n > n_)
212 16 n = n_;
213 27137 p_ += n;
214 27137 n_ -= n;
215 27137 return *this;
216 }
217
218 /// Slice customization point for `tag_invoke`.
219 friend
220 void
221 2640 tag_invoke(
222 slice_tag const&,
223 const_buffer& b,
224 slice_how how,
225 std::size_t n) noexcept
226 {
227 2640 b.do_slice(how, n);
228 2640 }
229
230 private:
231 2640 void do_slice(
232 slice_how how, std::size_t n) noexcept
233 {
234 2640 switch(how)
235 {
236 1313 case slice_how::remove_prefix:
237 1313 *this += n;
238 1313 return;
239
240 1327 case slice_how::keep_prefix:
241 1327 if( n < n_)
242 1238 n_ = n;
243 1327 return;
244 }
245 }
246 };
247
248 //------------------------------------------------
249
250 /** Concept for sequences of read-only buffer regions.
251
252 A type satisfies `ConstBufferSequence` if it represents one or more
253 contiguous memory regions that can be read. This includes single
254 buffers (convertible to `const_buffer`) and ranges of buffers.
255
256 @par Syntactic Requirements
257 @li Convertible to `const_buffer`, OR
258 @li A bidirectional range with value type convertible to `const_buffer`
259
260 @see const_buffer, MutableBufferSequence
261 */
262 template<typename T>
263 concept ConstBufferSequence =
264 std::is_convertible_v<T, const_buffer> || (
265 std::ranges::bidirectional_range<T> &&
266 std::is_convertible_v<std::ranges::range_value_t<T>, const_buffer>);
267
268 /** Concept for sequences of writable buffer regions.
269
270 A type satisfies `MutableBufferSequence` if it represents one or more
271 contiguous memory regions that can be written. This includes single
272 buffers (convertible to `mutable_buffer`) and ranges of buffers.
273 Every `MutableBufferSequence` also satisfies `ConstBufferSequence`.
274
275 @par Syntactic Requirements
276 @li Convertible to `mutable_buffer`, OR
277 @li A bidirectional range with value type convertible to `mutable_buffer`
278
279 @see mutable_buffer, ConstBufferSequence
280 */
281 template<typename T>
282 concept MutableBufferSequence =
283 std::is_convertible_v<T, mutable_buffer> || (
284 std::ranges::bidirectional_range<T> &&
285 std::is_convertible_v<std::ranges::range_value_t<T>, mutable_buffer>);
286
287 //------------------------------------------------------------------------------
288
289 /** Return an iterator to the first buffer in a sequence.
290
291 Handles single buffers and ranges uniformly. For a single buffer,
292 returns a pointer to it (forming a one-element range).
293 */
294 constexpr struct begin_mrdocs_workaround_t
295 {
296 template<std::convertible_to<const_buffer> ConvertibleToBuffer>
297 21794 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
298 {
299 21794 return std::addressof(b);
300 }
301
302 template<ConstBufferSequence BS>
303 requires (!std::convertible_to<BS, const_buffer>)
304 56166 auto operator()(BS const& bs) const noexcept
305 {
306 56166 return std::ranges::begin(bs);
307 }
308
309 template<ConstBufferSequence BS>
310 requires (!std::convertible_to<BS, const_buffer>)
311 18574 auto operator()(BS& bs) const noexcept
312 {
313 18574 return std::ranges::begin(bs);
314 }
315 } begin {};
316
317 /** Return an iterator past the last buffer in a sequence.
318
319 Handles single buffers and ranges uniformly. For a single buffer,
320 returns a pointer one past it.
321 */
322 constexpr struct end_mrdocs_workaround_t
323 {
324 template<std::convertible_to<const_buffer> ConvertibleToBuffer>
325 21554 auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
326 {
327 21554 return std::addressof(b) + 1;
328 }
329
330 template<ConstBufferSequence BS>
331 requires (!std::convertible_to<BS, const_buffer>)
332 48637 auto operator()(BS const& bs) const noexcept
333 {
334 48637 return std::ranges::end(bs);
335 }
336
337 template<ConstBufferSequence BS>
338 requires (!std::convertible_to<BS, const_buffer>)
339 18574 auto operator()(BS& bs) const noexcept
340 {
341 18574 return std::ranges::end(bs);
342 }
343 } end {};
344
345 //------------------------------------------------------------------------------
346
347 template<ConstBufferSequence CB>
348 std::size_t
349 13738 tag_invoke(
350 size_tag const&,
351 CB const& bs) noexcept
352 {
353 13738 std::size_t n = 0;
354 13738 auto const e = end(bs);
355 34617 for(auto it = begin(bs); it != e; ++it)
356 20879 n += const_buffer(*it).size();
357 13738 return n;
358 }
359
360 //------------------------------------------------------------------------------
361
362 /** Return the total byte count across all buffers in a sequence.
363
364 Sums the `size()` of each buffer in the sequence. This differs
365 from `buffer_length` which counts the number of buffer elements.
366
367 @par Example
368 @code
369 std::array<mutable_buffer, 2> bufs = { ... };
370 std::size_t total = buffer_size( bufs ); // sum of both sizes
371 @endcode
372 */
373 constexpr struct buffer_size_mrdocs_workaround_t
374 {
375 template<ConstBufferSequence CB>
376 19237 constexpr std::size_t operator()(
377 CB const& bs) const noexcept
378 {
379 19237 return tag_invoke(size_tag{}, bs);
380 }
381 } buffer_size {};
382
383 /** Check if a buffer sequence contains no data.
384
385 @return `true` if all buffers have size zero or the sequence
386 is empty.
387 */
388 constexpr struct buffer_empty_mrdocs_workaround_t
389 {
390 template<ConstBufferSequence CB>
391 4156 constexpr bool operator()(
392 CB const& bs) const noexcept
393 {
394 4156 auto it = begin(bs);
395 4156 auto const end_ = end(bs);
396 4211 while(it != end_)
397 {
398 4169 const_buffer b(*it++);
399 4169 if(b.size() != 0)
400 4114 return false;
401 }
402 42 return true;
403 }
404 } buffer_empty {};
405
406 //-----------------------------------------------
407
408 namespace detail {
409
410 template<class It>
411 auto
412 240 length_impl(It first, It last, int)
413 -> decltype(static_cast<std::size_t>(last - first))
414 {
415 240 return static_cast<std::size_t>(last - first);
416 }
417
418 template<class It>
419 std::size_t
420 length_impl(It first, It last, long)
421 {
422 std::size_t n = 0;
423 while(first != last)
424 {
425 ++first;
426 ++n;
427 }
428 return n;
429 }
430
431 } // detail
432
433 /** Return the number of buffer elements in a sequence.
434
435 Counts the number of individual buffer objects, not bytes.
436 For a single buffer, returns 1. For a range, returns the
437 distance from `begin` to `end`.
438
439 @see buffer_size
440 */
441 template<ConstBufferSequence CB>
442 std::size_t
443 240 buffer_length(CB const& bs)
444 {
445 240 return detail::length_impl(
446 240 begin(bs), end(bs), 0);
447 }
448
449 /// Alias for `mutable_buffer` or `const_buffer` based on sequence type.
450 template<typename BS>
451 using buffer_type = std::conditional_t<
452 MutableBufferSequence<BS>,
453 mutable_buffer, const_buffer>;
454
455 } // capy
456 } // boost
457
458 #endif
459