include/boost/capy/buffers.hpp

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