GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/buffers/buffer_param.hpp
Date: 2026-01-23 03:27:33
Exec Total Coverage
Lines: 108 118 91.5%
Functions: 6 6 100.0%
Branches: 0 0 -%

Line Branch Exec Source
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_BUFFER_PARAM_HPP
11 #define BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/buffers.hpp>
15 #include <boost/assert.hpp>
16
17 #include <array>
18 #include <span>
19 #include <type_traits>
20
21 namespace boost {
22 namespace capy {
23
24 /** A type-erased const buffer sequence for function call boundaries.
25
26 This class enables functions to accept any const buffer sequence
27 type across a non-template interface, while the caller retains
28 ownership of the underlying buffer data. The class provides
29 incremental access to the buffer sequence through a sliding
30 window, allowing efficient processing of arbitrarily large
31 sequences without copying all buffer descriptors upfront.
32
33 @par Purpose
34
35 When building I/O abstractions, a common need arises: accepting
36 buffer sequences of any type through a non-template function
37 signature. This class solves the type-erasure problem by storing
38 function pointers that operate on the concrete buffer sequence
39 type, while presenting a uniform interface to the callee.
40
41 @par Lifetime Model
42
43 @warning This class has DANGEROUS lifetime semantics. The safety
44 of this class depends entirely on the lifetime of the object
45 returned by @ref make_const_buffer_param. When that object is
46 destroyed, this class becomes invalid and any use is undefined
47 behavior.
48
49 The intended usage pattern exploits C++ temporary lifetime
50 extension: when passing the result of @ref make_const_buffer_param
51 directly to a function taking `const_buffer_param` by value,
52 slicing occurs and the temporary implementation object lives
53 until the function returns. This is safe:
54
55 @code
56 void write_some(const_buffer_param buffers);
57
58 // SAFE: temporary lives through the function call
59 write_some(make_const_buffer_param(my_buffers));
60 @endcode
61
62 @par Correct Usage
63
64 The implementation receiving `const_buffer_param` MUST:
65
66 @li Use `data()` and `consume()` immediately within the call
67 @li Never store the `const_buffer_param` object
68 @li Never return the `const_buffer_param` from the function
69 @li Complete all buffer processing before returning
70
71 @par Example: Correct Usage
72
73 @code
74 // Function accepts type-erased buffer parameter
75 std::size_t write_all(socket& sock, const_buffer_param buffers)
76 {
77 std::size_t total = 0;
78 while(true)
79 {
80 auto bufs = buffers.data();
81 if(bufs.empty())
82 break;
83 auto n = sock.write_some(bufs);
84 total += n;
85 buffers.consume(n);
86 }
87 return total;
88 }
89
90 // Caller creates temporary that lives through the call
91 std::vector<const_buffer> chunks = get_chunks();
92 auto n = write_all(sock, make_const_buffer_param(chunks));
93 @endcode
94
95 @par UNSAFE USAGE: Storing const_buffer_param
96
97 @warning Never store `const_buffer_param` for later use.
98
99 @code
100 class broken_writer
101 {
102 const_buffer_param saved_; // UNSAFE: member storage
103
104 void start_write(const_buffer_param buffers)
105 {
106 saved_ = buffers; // UNSAFE: storing for later
107 schedule_write();
108 }
109
110 void do_write()
111 {
112 // UNSAFE: The temporary from make_const_buffer_param
113 // was destroyed when start_write returned!
114 auto bufs = saved_.data(); // UNDEFINED BEHAVIOR
115 }
116 };
117 @endcode
118
119 @par UNSAFE USAGE: Returning const_buffer_param
120
121 @warning Never return `const_buffer_param` from a function.
122
123 @code
124 // UNSAFE: Returning causes the temporary to be destroyed
125 const_buffer_param get_buffers()
126 {
127 std::vector<const_buffer> bufs = prepare_buffers();
128 return make_const_buffer_param(bufs); // UNSAFE!
129 // The temporary AND the vector are destroyed here
130 }
131
132 void caller()
133 {
134 auto bp = get_buffers(); // Receives invalid object
135 bp.data(); // UNDEFINED BEHAVIOR
136 }
137 @endcode
138
139 @par UNSAFE USAGE: Storing in a Container
140
141 @warning Never store `const_buffer_param` in containers or
142 data structures.
143
144 @code
145 std::vector<const_buffer_param> pending; // UNSAFE
146
147 void queue_write(const_buffer_param buffers)
148 {
149 pending.push_back(buffers); // UNSAFE: storing
150 }
151
152 void process_pending()
153 {
154 for(auto& bp : pending)
155 bp.data(); // UNDEFINED BEHAVIOR
156 }
157 @endcode
158
159 @par UNSAFE USAGE: Assigning to a Variable
160
161 @warning Assigning to a named variable extends the wrong
162 lifetime.
163
164 @code
165 void broken()
166 {
167 auto bp = make_const_buffer_param(my_buffers);
168 // bp is valid here...
169
170 some_function(); // ...but if this modifies my_buffers...
171
172 bp.data(); // ...this may see inconsistent state
173 }
174
175 void also_broken()
176 {
177 const_buffer_param bp = make_const_buffer_param(
178 std::vector<const_buffer>{{data, size}});
179 // UNSAFE: The temporary vector is destroyed immediately!
180 bp.data(); // UNDEFINED BEHAVIOR
181 }
182 @endcode
183
184 @par Passing Convention
185
186 Pass by value. The class contains only three pointers (24 bytes
187 on 64-bit systems), making copies trivial. Pass-by-value also
188 makes the slicing behavior explicit at call sites.
189
190 @see make_const_buffer_param, mutable_buffer_param
191 */
192 class const_buffer_param
193 {
194 protected:
195 void* ctx_;
196 std::span<const_buffer>(*data_)(void*);
197 bool (*consume_)(void*, std::size_t);
198
199 public:
200 /** Return the current window of buffer descriptors.
201
202 Returns a span of buffer descriptors representing the
203 currently available portion of the buffer sequence. The
204 span may contain fewer buffers than the total sequence;
205 call @ref consume to advance through the sequence and
206 then call `data()` again to get the next window.
207
208 @return A span of const buffer descriptors. Empty span
209 indicates no more data is available.
210 */
211 std::span<const_buffer>
212 19 data() const noexcept
213 {
214 19 return data_(ctx_);
215 }
216
217 /** Consume bytes from the buffer sequence.
218
219 Advances the current position by `n` bytes, consuming
220 data from the front of the sequence. After consuming,
221 call @ref data to get the updated window of remaining
222 buffers.
223
224 @param n Number of bytes to consume.
225
226 @return `true` if more data remains in the sequence,
227 `false` if the sequence is exhausted.
228 */
229 bool
230 12 consume(std::size_t n)
231 {
232 12 return consume_(ctx_, n);
233 }
234 };
235
236 /** A type-erased mutable buffer sequence for function call boundaries.
237
238 This class is identical to @ref const_buffer_param except it
239 operates on mutable buffer sequences, allowing the callee to
240 write into the buffers.
241
242 @warning This class has the same dangerous lifetime semantics
243 as @ref const_buffer_param. Read that documentation carefully
244 before using this class.
245
246 @see make_buffer_param, const_buffer_param
247 */
248 class mutable_buffer_param
249 {
250 protected:
251 void* ctx_;
252 std::span<mutable_buffer>(*data_)(void*);
253 bool (*consume_)(void*, std::size_t);
254
255 public:
256 /** Return the current window of buffer descriptors.
257
258 @return A span of mutable buffer descriptors. Empty span
259 indicates no more data is available.
260
261 @see const_buffer_param::data
262 */
263 std::span<mutable_buffer>
264 4 data() const noexcept
265 {
266 4 return data_(ctx_);
267 }
268
269 /** Consume bytes from the buffer sequence.
270
271 @param n Number of bytes to consume.
272
273 @return `true` if more data remains in the sequence,
274 `false` if the sequence is exhausted.
275
276 @see const_buffer_param::consume
277 */
278 bool
279 3 consume(std::size_t n)
280 {
281 3 return consume_(ctx_, n);
282 }
283 };
284
285 /** Create a const buffer parameter from a buffer sequence.
286
287 Constructs a type-erased buffer parameter that provides
288 incremental access to the given buffer sequence. The returned
289 object inherits from @ref const_buffer_param and can be passed
290 to functions accepting that type by value (causing slicing).
291
292 @warning The returned object contains pointers to internal
293 state. The object MUST remain alive while any sliced
294 @ref const_buffer_param copy is in use. The intended pattern
295 is to pass the result directly to a function:
296
297 @code
298 void process(const_buffer_param bp);
299
300 // CORRECT: temporary lives through the call
301 process(make_const_buffer_param(buffers));
302
303 // DANGEROUS: storing extends the wrong lifetime
304 auto bp = make_const_buffer_param(buffers);
305 process(bp); // Works, but fragile
306 @endcode
307
308 @par Thread Safety
309 Not thread-safe. The returned object and any sliced copies
310 must not be used concurrently.
311
312 @param bs The buffer sequence to wrap. Must remain valid
313 for the lifetime of the returned object.
314
315 @return An implementation object that inherits from
316 @ref const_buffer_param.
317 */
318 template<ConstBufferSequence BS>
319 [[nodiscard]] auto
320 16 make_const_buffer_param(BS const& bs)
321 {
322 class impl : public const_buffer_param
323 {
324 std::array<const_buffer, 16> arr_;
325 decltype(begin(std::declval<BS const&>())) it_;
326 decltype(end(std::declval<BS const&>())) end_;
327 std::size_t size_ = 0;
328 std::size_t pos_ = 0;
329 std::size_t offset_ = 0;
330
331 void
332 18 refill_window()
333 {
334 18 pos_ = 0;
335 18 size_ = 0;
336 18 offset_ = 0;
337 81 for(; it_ != end_ && size_ < 16; ++it_)
338 {
339 63 const_buffer buf(*it_);
340 63 if(buf.size() == 0)
341 4 continue;
342 59 arr_[size_++] = buf;
343 }
344 18 }
345
346 static
347 std::span<const_buffer>
348 19 data_impl(void* ctx)
349 {
350 19 auto& self = *static_cast<impl*>(ctx);
351 19 if(self.pos_ >= self.size_)
352 7 self.refill_window();
353 19 if(self.size_ == 0)
354 7 return {};
355 12 if(self.offset_ > 0)
356 {
357 2 auto& buf = self.arr_[self.pos_];
358 6 buf = const_buffer(
359 2 static_cast<char const*>(buf.data()) + self.offset_,
360 2 buf.size() - self.offset_);
361 2 self.offset_ = 0;
362 }
363 24 return { self.arr_.data() + self.pos_, self.size_ - self.pos_ };
364 }
365
366 static
367 bool
368 12 consume_impl(void* ctx, std::size_t n)
369 {
370 12 auto& self = *static_cast<impl*>(ctx);
371 73 while(n > 0 && self.pos_ < self.size_)
372 {
373 61 auto const avail = self.arr_[self.pos_].size() - self.offset_;
374 61 if(n < avail)
375 {
376 3 self.offset_ += n;
377 3 n = 0;
378 }
379 else
380 {
381 58 n -= avail;
382 58 self.offset_ = 0;
383 58 ++self.pos_;
384 }
385 }
386 12 if(self.pos_ >= self.size_ && self.it_ != self.end_)
387 3 self.refill_window();
388 12 return self.pos_ < self.size_;
389 }
390
391 static
392 std::span<const_buffer>
393 data_after_destroy(void*)
394 {
395 BOOST_ASSERT(!"const_buffer_param used after impl destroyed");
396 return {};
397 }
398
399 static
400 bool
401 consume_after_destroy(void*, std::size_t)
402 {
403 BOOST_ASSERT(!"const_buffer_param used after impl destroyed");
404 return false;
405 }
406
407 public:
408 impl(impl const&) = delete;
409 impl(impl&&) = delete;
410 impl& operator=(impl const&) = delete;
411 impl& operator=(impl&&) = delete;
412
413 explicit
414 8 impl(BS const& bs)
415 8 : it_(begin(bs))
416 16 , end_(end(bs))
417 {
418 8 ctx_ = this;
419 8 data_ = &data_impl;
420 8 consume_ = &consume_impl;
421 8 refill_window();
422 8 }
423
424 8 ~impl()
425 {
426 8 ctx_ = nullptr;
427 8 data_ = &data_after_destroy;
428 8 consume_ = &consume_after_destroy;
429 8 }
430 };
431
432 16 return impl(bs);
433 }
434
435 /** @copydoc make_const_buffer_param(BS const&)
436 @note Deleted overload to prevent passing temporary buffer sequences.
437 */
438 template<class BS>
439 requires ConstBufferSequence<std::remove_cvref_t<BS>> &&
440 (!std::is_lvalue_reference_v<BS>)
441 auto
442 make_const_buffer_param(BS&&) = delete;
443
444 /** Create a mutable buffer parameter from a buffer sequence.
445
446 Constructs a type-erased buffer parameter that provides
447 incremental access to the given mutable buffer sequence.
448
449 @warning The returned object has the same dangerous lifetime
450 semantics as @ref make_const_buffer_param. Read that
451 documentation carefully.
452
453 @par Thread Safety
454 Not thread-safe.
455
456 @param bs The mutable buffer sequence to wrap. Must remain
457 valid for the lifetime of the returned object.
458
459 @return An implementation object that inherits from
460 @ref mutable_buffer_param.
461
462 @see make_const_buffer_param, mutable_buffer_param
463 */
464 template<MutableBufferSequence BS>
465 [[nodiscard]] auto
466 2 make_buffer_param(BS const& bs)
467 {
468 class impl : public mutable_buffer_param
469 {
470 std::array<mutable_buffer, 16> arr_;
471 decltype(begin(std::declval<BS const&>())) it_;
472 decltype(end(std::declval<BS const&>())) end_;
473 std::size_t size_ = 0;
474 std::size_t pos_ = 0;
475 std::size_t offset_ = 0;
476
477 void
478 3 refill_window()
479 {
480 3 pos_ = 0;
481 3 size_ = 0;
482 3 offset_ = 0;
483 7 for(; it_ != end_ && size_ < 16; ++it_)
484 {
485 4 mutable_buffer buf(*it_);
486 4 if(buf.size() == 0)
487 continue;
488 4 arr_[size_++] = buf;
489 }
490 3 }
491
492 static
493 std::span<mutable_buffer>
494 4 data_impl(void* ctx)
495 {
496 4 auto& self = *static_cast<impl*>(ctx);
497 4 if(self.pos_ >= self.size_)
498 1 self.refill_window();
499 4 if(self.size_ == 0)
500 1 return {};
501 3 if(self.offset_ > 0)
502 {
503 1 auto& buf = self.arr_[self.pos_];
504 3 buf = mutable_buffer(
505 1 static_cast<char*>(buf.data()) + self.offset_,
506 1 buf.size() - self.offset_);
507 1 self.offset_ = 0;
508 }
509 6 return { self.arr_.data() + self.pos_, self.size_ - self.pos_ };
510 }
511
512 static
513 bool
514 3 consume_impl(void* ctx, std::size_t n)
515 {
516 3 auto& self = *static_cast<impl*>(ctx);
517 8 while(n > 0 && self.pos_ < self.size_)
518 {
519 5 auto const avail = self.arr_[self.pos_].size() - self.offset_;
520 5 if(n < avail)
521 {
522 2 self.offset_ += n;
523 2 n = 0;
524 }
525 else
526 {
527 3 n -= avail;
528 3 self.offset_ = 0;
529 3 ++self.pos_;
530 }
531 }
532 3 if(self.pos_ >= self.size_ && self.it_ != self.end_)
533 self.refill_window();
534 3 return self.pos_ < self.size_;
535 }
536
537 static
538 std::span<mutable_buffer>
539 data_after_destroy(void*)
540 {
541 BOOST_ASSERT(!"mutable_buffer_param used after impl destroyed");
542 return {};
543 }
544
545 static
546 bool
547 consume_after_destroy(void*, std::size_t)
548 {
549 BOOST_ASSERT(!"mutable_buffer_param used after impl destroyed");
550 return false;
551 }
552
553 public:
554 impl(impl const&) = delete;
555 impl(impl&&) = delete;
556 impl& operator=(impl const&) = delete;
557 impl& operator=(impl&&) = delete;
558
559 explicit
560 2 impl(BS const& bs)
561 2 : it_(begin(bs))
562 4 , end_(end(bs))
563 {
564 2 ctx_ = this;
565 2 data_ = &data_impl;
566 2 consume_ = &consume_impl;
567 2 refill_window();
568 2 }
569
570 2 ~impl()
571 {
572 2 ctx_ = nullptr;
573 2 data_ = &data_after_destroy;
574 2 consume_ = &consume_after_destroy;
575 2 }
576 };
577
578 2 return impl(bs);
579 }
580
581 /** @copydoc make_buffer_param(BS const&)
582 @note Deleted overload to prevent passing temporary buffer sequences.
583 */
584 template<class BS>
585 requires MutableBufferSequence<std::remove_cvref_t<BS>> &&
586 (!std::is_lvalue_reference_v<BS>)
587 auto
588 make_buffer_param(BS&&) = delete;
589
590 } // namespace capy
591 } // namespace boost
592
593 #endif
594