LCOV - code coverage report
Current view: top level - boost/capy/buffers - buffer_param.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 91.5 % 118 108
Test Date: 2026-01-23 03:27:33 Functions: 77.8 % 36 28

            Line data    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_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            8 : 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            0 :         data_after_destroy(void*)
     394              :         {
     395            0 :             BOOST_ASSERT(!"const_buffer_param used after impl destroyed");
     396              :             return {};
     397              :         }
     398              : 
     399              :         static
     400              :         bool
     401            0 :         consume_after_destroy(void*, std::size_t)
     402              :         {
     403            0 :             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            8 :     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            0 :                     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            0 :                 self.refill_window();
     534            3 :             return self.pos_ < self.size_;
     535              :         }
     536              : 
     537              :         static
     538              :         std::span<mutable_buffer>
     539            0 :         data_after_destroy(void*)
     540              :         {
     541            0 :             BOOST_ASSERT(!"mutable_buffer_param used after impl destroyed");
     542              :             return {};
     543              :         }
     544              : 
     545              :         static
     546              :         bool
     547            0 :         consume_after_destroy(void*, std::size_t)
     548              :         {
     549            0 :             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
        

Generated by: LCOV version 2.3