LCOV - code coverage report
Current view: top level - boost/buffers - buffer.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 88 88
Test Date: 2025-12-06 02:12:43 Functions: 100.0 % 115 115

            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/buffers
       8              : //
       9              : 
      10              : #ifndef BOOST_BUFFERS_BUFFER_HPP
      11              : #define BOOST_BUFFERS_BUFFER_HPP
      12              : 
      13              : #include <boost/buffers/detail/config.hpp>
      14              : #include <boost/buffers/detail/type_traits.hpp>
      15              : #include <cstddef>
      16              : #include <iterator>
      17              : #include <memory>
      18              : #include <type_traits>
      19              : 
      20              : // https://www.boost.org/doc/libs/1_65_0/doc/html/boost_asio/reference/ConstBufferSequence.html
      21              : 
      22              : // GCC 6 and earlier bug fixed after PR 42329
      23              : // ("deduction of template template argument via base class fails")
      24              : #if defined(__GNUC__) && ! defined(__clang__) && (__GNUC__ < 7)
      25              : #define BOOST_BUFFERS_GCC6_WORKAROUND
      26              : #endif
      27              : 
      28              : namespace boost {
      29              : 
      30              : namespace asio {
      31              : class const_buffer;
      32              : class mutable_buffer;
      33              : } // asio
      34              : 
      35              : namespace buffers {
      36              : 
      37              : #ifndef BOOST_BUFFERS_GCC6_WORKAROUND
      38              : 
      39              : class const_buffer;
      40              : class mutable_buffer;
      41              : 
      42              : namespace detail {
      43              : 
      44              : // this is designed to satisfy Asio's buffer constructors
      45              : template<class T, std::size_t Extent = (std::size_t)(-1)>
      46              : class basic_buffer
      47              : {
      48              : public:
      49              :     /** Return a pointer to the beginning of the memory region
      50              :     */
      51              :     constexpr auto
      52     13848866 :     data() const noexcept ->
      53              :         typename std::conditional<
      54              :             std::is_const<T>::value,
      55              :             void const*, void*>::type
      56              :     {
      57     13848866 :         return p_;
      58              :     }
      59              : 
      60              :     /** Return the number of valid bytes in the referenced memory region
      61              :     */
      62     20494954 :     constexpr std::size_t size() const noexcept
      63              :     {
      64     20494954 :         return n_;
      65              :     }
      66              : 
      67              : private:
      68              :     friend class buffers::const_buffer;
      69              :     friend class buffers::mutable_buffer;
      70              :     friend class asio::const_buffer;
      71              :     friend class asio::mutable_buffer;
      72       147653 :     basic_buffer() = default;
      73      5307869 :     constexpr basic_buffer(T* p, std::size_t n) noexcept : p_(p), n_(n) {}
      74              :     constexpr basic_buffer<T, (std::size_t)(-1)> subspan(
      75              :         std::size_t, std::size_t = (std::size_t)(-1)) const noexcept;
      76              : 
      77              :     T* p_ = nullptr;
      78              :     std::size_t n_ = 0;
      79              : };
      80              : 
      81              : } // detail
      82              : 
      83              : #endif
      84              : 
      85              : //-----------------------------------------------
      86              : 
      87              : namespace detail {
      88              : namespace adl {
      89              : 
      90              : using std::begin;
      91              : template<class T>
      92              : using begin_type = decltype(begin(std::declval<T&>()));
      93              : 
      94              : using std::end;
      95              : template<class T>
      96              : using end_type = decltype(end(std::declval<T&>()));
      97              : 
      98              : // determine if T is a bidirectional range
      99              : // whose value type is convertible to B
     100              : template<class T, class B, class = void>
     101              : struct is_bidir_of : std::false_type
     102              : {
     103              : };
     104              : 
     105              : template<class T, class B>
     106              : struct is_bidir_of<T, B, detail::void_t<typename std::enable_if<
     107              :     std::is_convertible<decltype(*std::declval<begin_type<T const>>()), B>::value &&
     108              :     std::is_convertible<decltype(*std::declval<end_type<T const>>()), B>::value &&
     109              :     detail::is_bidirectional_iterator<begin_type<T const>>::value &&
     110              :     detail::is_bidirectional_iterator<end_type<T const>>::value &&
     111              :     std::is_same<
     112              :         decltype(begin(std::declval<T const>())),
     113              :         decltype(end  (std::declval<T const>()))>::value
     114              :     >::type > >
     115              :     : std::true_type
     116              : {
     117              : };
     118              : 
     119              : } // adl
     120              : } // detail
     121              : 
     122              : //------------------------------------------------
     123              : 
     124              : /** size tag for `tag_invoke`
     125              : 
     126              :     This type is used in overloads of `tag_invoke`
     127              :     for user-defined types to customize the `size()`
     128              :     algorithm.
     129              : */
     130              : struct size_tag {};
     131              : 
     132              : /** slice tag for `tag_invoke`
     133              : 
     134              :     This type is used in overloads of `tag_invoke`
     135              :     for user-defined types to customize the slicing
     136              :     algorithms.
     137              : */
     138              : struct slice_tag {};
     139              : 
     140              : /** slice constants for slice customization
     141              : 
     142              :     This defines the possible values passed to
     143              :     overloads of `tag_invoke` for user-defined
     144              :     types which customize the slicing algorithms.
     145              : */
     146              : enum class slice_how
     147              : {
     148              :     /// Indicates that the front of the buffer sequence should be trimmed
     149              :     remove_prefix,
     150              : 
     151              :     /// Indicates that the front of the buffer sequence should be preserved
     152              :     keep_prefix
     153              : };
     154              : 
     155              : //------------------------------------------------
     156              : 
     157              : #ifndef BOOST_BUFFERS_GCC6_WORKAROUND
     158              : 
     159              : /** Holds a contiguous range of modifiable bytes
     160              : */
     161              : class mutable_buffer
     162              :     : public detail::basic_buffer<unsigned char>
     163              : {
     164              : public:
     165              :     /** Constructor.
     166              :     */
     167          578 :     mutable_buffer() = default;
     168              : 
     169              :     /** Constructor.
     170              :     */
     171              :     mutable_buffer(
     172              :         mutable_buffer const&) = default;
     173              : 
     174              :     /** Assignment.
     175              :     */
     176              :     mutable_buffer& operator=(
     177              :         mutable_buffer const&) = default;
     178              : 
     179              :     /** Constructor.
     180              :     */
     181       638627 :     constexpr mutable_buffer(
     182              :         void* data, std::size_t size) noexcept
     183       638627 :         : basic_buffer<unsigned char>(
     184       638627 :             static_cast<unsigned char*>(data), size)
     185              :     {
     186       638627 :     }
     187              : 
     188              :     /** Constructor
     189              :     */
     190              :     template<class MutableBuffer, class = typename std::enable_if<
     191              :         std::is_same<MutableBuffer, asio::mutable_buffer>::value>::type>
     192              :     constexpr mutable_buffer(
     193              :         MutableBuffer const& b) noexcept
     194              :         : basic_buffer<unsigned char>(
     195              :             static_cast<unsigned char*>(
     196              :                 b.data()), b.size())
     197              :     {
     198              :     }
     199              : 
     200              :     /** Remove a prefix of the memory region
     201              : 
     202              :         If the requested number of bytes is larger than the current size,
     203              :         the resulting buffer will have size 0.
     204              : 
     205              :         @param n The number of bytes to remove.
     206              :     */
     207              :     mutable_buffer&
     208      1025106 :     operator+=(std::size_t n) noexcept
     209              :     {
     210      1025106 :         if( n > n_)
     211           16 :             n = n_;
     212      1025106 :         p_ += n;
     213      1025106 :         n_ -= n;
     214      1025106 :         return *this;
     215              :     }
     216              : 
     217              :     /** Remove a slice from the buffer
     218              :     */
     219              :     friend
     220              :     void
     221         1088 :     tag_invoke(
     222              :         slice_tag const&,
     223              :         mutable_buffer& b,
     224              :         slice_how how,
     225              :         std::size_t n) noexcept
     226              :     {
     227         1088 :         b.do_slice(how, n);
     228         1088 :     }
     229              : 
     230              : private:
     231         1088 :     void do_slice(
     232              :         slice_how how, std::size_t n) noexcept
     233              :     {
     234         1088 :         switch(how)
     235              :         {
     236          512 :         case slice_how::remove_prefix:
     237          512 :             *this += n;
     238          512 :             return;
     239              : 
     240          576 :         case slice_how::keep_prefix:
     241          576 :             if( n < n_)
     242          476 :                 n_ = n;
     243          576 :             return;
     244              :         }
     245              :     }
     246              : };
     247              : 
     248              : //------------------------------------------------
     249              : 
     250              : /** Holds a contiguous range of unmodifiable bytes
     251              : */
     252              : class const_buffer
     253              :     : public detail::basic_buffer<unsigned char const>
     254              : {
     255              : public:
     256              :     /** Constructor
     257              :     */
     258       147075 :     const_buffer() = default;
     259              : 
     260              :     /** Constructor
     261              :     */
     262              :     const_buffer(const_buffer const&) = default;
     263              : 
     264              :     /** Assignment
     265              : 
     266              :         @par Postconditions
     267              :         @code
     268              :         this->data() == other.data() && this->size() == other.size()
     269              :         @endcode
     270              :     */
     271              :     const_buffer& operator=(
     272              :         const_buffer const& other) = default;
     273              : 
     274              :     /** Constructor
     275              :     */
     276      4660271 :     constexpr const_buffer(
     277              :         void const* data, std::size_t size) noexcept
     278      4660271 :         : basic_buffer<unsigned char const>(
     279      4660271 :             static_cast<unsigned char const*>(data), size)
     280              :     {
     281      4660271 :     }
     282              : 
     283              :     /** Constructor
     284              :     */
     285         8971 :     constexpr const_buffer(
     286              :         mutable_buffer const& b) noexcept
     287         8971 :         : basic_buffer<unsigned char const>(
     288         8971 :             static_cast<unsigned char const*>(b.data()), b.size())
     289              :     {
     290         8971 :     }
     291              : 
     292              :     /** Constructor
     293              :     */
     294              :     template<class ConstBuffer, class = typename std::enable_if<
     295              :         std::is_same<ConstBuffer, asio::const_buffer>::value ||
     296              :         std::is_same<ConstBuffer, asio::mutable_buffer>::value>::type>
     297              :     constexpr const_buffer(
     298              :         ConstBuffer const& b) noexcept
     299              :         : basic_buffer<unsigned char const>(
     300              :             static_cast<unsigned char const*>(
     301              :                 b.data()), b.size())
     302              :     {
     303              :     }
     304              : 
     305              :     /** Remove a prefix of the memory region
     306              : 
     307              :         If the requested number of bytes is larger than the current size,
     308              :         the resulting buffer will have size 0.
     309              : 
     310              :         @param n The number of bytes to remove.
     311              :     */
     312              :     const_buffer&
     313      1156247 :     operator+=(std::size_t n) noexcept
     314              :     {
     315      1156247 :         if( n > n_)
     316         4112 :             n = n_;
     317      1156247 :         p_ += n;
     318      1156247 :         n_ -= n;
     319      1156247 :         return *this;
     320              :     }
     321              : 
     322              :     /** Remove a slice from the buffer
     323              :     */
     324              :     friend
     325              :     void
     326       267349 :     tag_invoke(
     327              :         slice_tag const&,
     328              :         const_buffer& b,
     329              :         slice_how how,
     330              :         std::size_t n) noexcept
     331              :     {
     332       267349 :         b.do_slice(how, n);
     333       267349 :     }
     334              : 
     335              : private:
     336       267349 :     void do_slice(
     337              :         slice_how how, std::size_t n) noexcept
     338              :     {
     339       267349 :         switch(how)
     340              :         {
     341       131653 :         case slice_how::remove_prefix:
     342       131653 :             *this += n;
     343       131653 :             return;
     344              : 
     345       135696 :         case slice_how::keep_prefix:
     346       135696 :             if( n < n_)
     347       121169 :                 n_ = n;
     348       135696 :             return;
     349              :         }
     350              :     }
     351              : };
     352              : 
     353              : #else
     354              : 
     355              : template<class T, std::size_t Extent = (std::size_t)(-1)>
     356              : class basic_buffer
     357              : {
     358              :     using pointer = typename std::conditional<
     359              :         std::is_const<T>::value, void const*, void*>::type;
     360              : public:
     361              :     basic_buffer() = default;
     362              :     basic_buffer(basic_buffer const&) = default;
     363              :     basic_buffer& operator=(basic_buffer const& other) = default;
     364              :     constexpr auto data() const noexcept -> pointer { return p_; }
     365              :     constexpr std::size_t size() const noexcept { return n_; }
     366              :     constexpr basic_buffer(pointer p, std::size_t n) noexcept
     367              :         : p_(p) , n_(n) {}
     368              :     template<class U, std::size_t E, class = typename std::enable_if<
     369              :         std::is_const<T>::value && ! std::is_const<U>::value>::type>
     370              :     constexpr basic_buffer(basic_buffer<U,E> b) noexcept
     371              :         : p_(b.data()), n_(b.size()) {}
     372              :     template<class Buffer, class = typename std::enable_if<
     373              :         std::is_same<Buffer, asio::mutable_buffer>::value ||
     374              :         (std::is_same<Buffer, asio::const_buffer>::value &&
     375              :             std::is_const<T>::value)>::type>
     376              :     constexpr basic_buffer(Buffer b) noexcept
     377              :         : p_(b.data()), n_(b.size()) {}
     378              :     basic_buffer& operator+=(std::size_t n) noexcept
     379              :     {
     380              :         if( n > n_)
     381              :             n = n_;
     382              :         p_ = static_cast<T*>(p_) + n;
     383              :         n_ -= n;
     384              :         return *this;
     385              :     }
     386              :     friend void tag_invoke(slice_tag const&, basic_buffer& b,
     387              :         slice_how how, std::size_t n) noexcept
     388              :     {
     389              :         switch(how)
     390              :         {
     391              :         case slice_how::remove_prefix:
     392              :             b += n;
     393              :             return;
     394              : 
     395              :         case slice_how::keep_prefix:
     396              :             if(n < b.n_)
     397              :                 b.n_ = n;
     398              :             return;
     399              :         }
     400              :     }
     401              : 
     402              : private:
     403              :     friend class asio::const_buffer;
     404              :     friend class asio::mutable_buffer;
     405              :     constexpr basic_buffer<T, (std::size_t)(-1)>
     406              :     subspan(std::size_t, std::size_t = (std::size_t)(-1)) const noexcept;
     407              : 
     408              :     pointer p_ = nullptr;
     409              :     std::size_t n_ = 0;
     410              : };
     411              : 
     412              : using mutable_buffer = basic_buffer<unsigned char>;
     413              : using const_buffer = basic_buffer<unsigned char const>;
     414              : 
     415              : #endif
     416              : 
     417              : //------------------------------------------------------------------------------
     418              : 
     419              : /** Return an iterator pointing to the first element of a buffer sequence
     420              : 
     421              :     This function returns an iterator to the beginning of the range denoted by
     422              :     `t`. While this works for any valid range, it is provided for convenience
     423              :     when using C++17 and earlier, where `std::ranges::begin` is unavailable.
     424              : 
     425              :     @par Constraints
     426              :     @code
     427              :     requires std::ranges::range<T>
     428              :     @endcode
     429              : 
     430              :     @param t The buffer sequence
     431              : */
     432              : constexpr struct begin_mrdocs_workaround_t
     433              : {
     434              :     template<
     435              :         class ConvertibleToBuffer
     436              :         ,class = typename std::enable_if<
     437              :             std::is_convertible<ConvertibleToBuffer, const_buffer>::value ||
     438              :             std::is_convertible<ConvertibleToBuffer, mutable_buffer>::value
     439              :         >::type
     440              :     >
     441       592836 :     auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
     442              :     {
     443       592836 :         return std::addressof(b);
     444              :     }
     445              : 
     446              :     template<
     447              :         class BufferSequence
     448              :         ,class = typename std::enable_if<
     449              :             ! std::is_convertible<BufferSequence const*, const_buffer const*>::value &&
     450              :             ! std::is_convertible<BufferSequence const*, mutable_buffer const*>::value,
     451              :             detail::void_t< detail::adl::begin_type<BufferSequence const> > >::type
     452              :     >
     453      4318667 :     auto operator()(BufferSequence const& bs) const noexcept
     454              : #if ! (__cpp_decltype_auto >= 201304)
     455              :         -> detail::adl::begin_type<BufferSequence const>
     456              : #endif
     457              :     {
     458              :         using std::begin;
     459      4318667 :         return begin(bs);
     460              :     }
     461              : 
     462              :     template<
     463              :         class BufferSequence
     464              :         ,class = typename std::enable_if<
     465              :             ! std::is_convertible<BufferSequence const*, const_buffer const*>::value &&
     466              :             ! std::is_convertible<BufferSequence const*, mutable_buffer const*>::value,
     467              :             detail::void_t< detail::adl::begin_type<BufferSequence> > >::type
     468              :     >
     469      2549261 :     auto operator()(BufferSequence& bs) const noexcept
     470              : #if ! (__cpp_decltype_auto >= 201304)
     471              :         -> detail::adl::begin_type<BufferSequence>
     472              : #endif
     473              :     {
     474              :         using std::begin;
     475      2549261 :         return begin(bs);
     476              :     }
     477              : } begin {};
     478              : 
     479              : //------------------------------------------------------------------------------
     480              : 
     481              : /** Return an iterator to the end of the buffer sequence
     482              : 
     483              :     This function returns an iterator to the end of the range denoted by
     484              :     `t`. While this works for any valid range, it is provided for convenience
     485              :     when using C++17 and earlier, where `std::ranges::end` is unavailable.
     486              : 
     487              :     @par Constraints
     488              :     @code
     489              :     requires std::ranges::range<T>
     490              :     @endcode
     491              : 
     492              :     @param t The buffer sequence
     493              : */
     494              : constexpr struct end_mrdocs_workaround_t
     495              : {
     496              :     template<
     497              :         class ConvertibleToBuffer
     498              :         ,class = typename std::enable_if<
     499              :             std::is_convertible<ConvertibleToBuffer, const_buffer>::value ||
     500              :             std::is_convertible<ConvertibleToBuffer, mutable_buffer>::value
     501              :         >::type
     502              :     >
     503       592836 :     auto operator()(ConvertibleToBuffer const& b) const noexcept -> ConvertibleToBuffer const*
     504              :     {
     505       592836 :         return std::addressof(b) + 1;
     506              :     }
     507              : 
     508              :     template<
     509              :         class BufferSequence
     510              :         ,class = typename std::enable_if<
     511              :             ! std::is_convertible<BufferSequence const*, const_buffer const*>::value &&
     512              :             ! std::is_convertible<BufferSequence const*, mutable_buffer const*>::value,
     513              :             detail::void_t< detail::adl::end_type<BufferSequence const> > >::type
     514              :     >
     515      4318665 :     auto operator()(BufferSequence const& bs) const noexcept
     516              : #if ! (__cpp_decltype_auto >= 201304)
     517              :         -> detail::adl::end_type<BufferSequence const>
     518              : #endif
     519              :     {
     520              :         using std::end;
     521      4318665 :         return end(bs);
     522              :     }
     523              : 
     524              :     template<
     525              :         class BufferSequence
     526              :         ,class = typename std::enable_if<
     527              :             ! std::is_convertible<BufferSequence const*, const_buffer const*>::value &&
     528              :             ! std::is_convertible<BufferSequence const*, mutable_buffer const*>::value,
     529              :             detail::void_t< detail::adl::end_type<BufferSequence> > >::type
     530              :     >
     531      2549261 :     auto operator()(BufferSequence& bs) const noexcept
     532              : #if ! (__cpp_decltype_auto >= 201304)
     533              :         -> detail::adl::end_type<BufferSequence>
     534              : #endif
     535              :     {
     536              :         using std::end;
     537      2549261 :         return end(bs);
     538              :     }
     539              : } end {};
     540              : 
     541              : //------------------------------------------------
     542              : 
     543              : /** Determine if T is a ConstBufferSequence.
     544              : */
     545              : /** @{ */
     546              : template<class T, class = void>
     547              : struct is_const_buffer_sequence
     548              :     : std::false_type
     549              : {
     550              : };
     551              : 
     552              : template<class T>
     553              : struct is_const_buffer_sequence<T>
     554              :     : std::integral_constant<bool,
     555              :         detail::adl::is_bidir_of<typename std::remove_cv<typename
     556              :             std::remove_reference<T>::type>::type, const_buffer>::value ||
     557              :         std::is_convertible<T, const_buffer>::value>
     558              : {
     559              : };
     560              : 
     561              : #if defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304
     562              : template<class T>
     563              : constexpr bool is_const_buffer_sequence_v =
     564              :     is_const_buffer_sequence<T>::value;
     565              : #endif
     566              : /** @} */
     567              : 
     568              : //------------------------------------------------
     569              : 
     570              : /** Determine if T is a MutableBufferSequence.
     571              : */
     572              : /** @{ */
     573              : template<class T, class = void>
     574              : struct is_mutable_buffer_sequence : std::false_type
     575              : {
     576              : };
     577              : 
     578              : template<class T>
     579              : struct is_mutable_buffer_sequence<T>
     580              :     : std::integral_constant<bool,
     581              :         detail::adl::is_bidir_of<typename std::remove_cv<typename
     582              :             std::remove_reference<T>::type>::type, mutable_buffer>::value ||
     583              :         std::is_convertible<T, mutable_buffer>::value>
     584              : {
     585              : };
     586              : 
     587              : #if defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304
     588              : template<class T>
     589              : constexpr bool is_mutable_buffer_sequence_v =
     590              :     is_mutable_buffer_sequence<T>::value;
     591              : #endif
     592              : /** @} */
     593              : 
     594              : //------------------------------------------------------------------------------
     595              : 
     596              : template<class ConstBufferSequence>
     597              : std::size_t
     598      1406869 : tag_invoke(
     599              :     size_tag const&,
     600              :     ConstBufferSequence const& bs) noexcept
     601              : {
     602      1406869 :     std::size_t n = 0;
     603      1406869 :     auto const e = end(bs);
     604      3884472 :     for(auto it = begin(bs); it != e; ++it)
     605      2477603 :         n += const_buffer(*it).size();
     606      1406874 :     return n;
     607            5 : }
     608              : 
     609              : /** Return the total number of bytes in a buffer sequence
     610              : 
     611              :     This function returns the sum of the number of bytes in each contiguous
     612              :     buffer contained in the range or value. This is different from the length
     613              :     of the sequence returned by `std::ranges::size(t)`
     614              : 
     615              :     @par Constraints
     616              :     @code
     617              :     is_const_buffer_sequence_v<T> || std::is_convertible<T,const_buffer>
     618              :     @endcode
     619              : 
     620              :     @par Example
     621              :     @code
     622              :     template<class ConstBufferSequence>
     623              :     bool is_small( ConstBufferSequence const& bs ) noexcept
     624              :     {
     625              :         return size(bs) < 100;
     626              :     }
     627              :     @endcode
     628              : */
     629              : constexpr struct size_mrdocs_workaround_t
     630              : {
     631              :     template<class ConstBufferSequence>
     632      1406869 :     constexpr auto operator()(
     633              :         ConstBufferSequence const& bs) const noexcept ->
     634              :             typename std::enable_if<
     635              :                 is_const_buffer_sequence<ConstBufferSequence>::value,
     636              :                 std::size_t>::type
     637              :     {
     638      1406869 :         return tag_invoke(size_tag{}, bs);
     639              :     }
     640              : } size {};
     641              : 
     642              : //-----------------------------------------------
     643              : 
     644              : namespace detail {
     645              : 
     646              : template<class It>
     647              : auto
     648            3 : length_impl(It first, It last, int)
     649              :     -> decltype(static_cast<std::size_t>(last - first))
     650              : {
     651            3 :     return static_cast<std::size_t>(last - first);
     652              : }
     653              : 
     654              : template<class It>
     655              : std::size_t
     656           21 : length_impl(It first, It last, long)
     657              : {
     658           21 :     std::size_t n = 0;
     659           30 :     while(first != last)
     660              :     {
     661            9 :         ++first;
     662            9 :         ++n;
     663              :     }
     664           21 :     return n;
     665              : }
     666              : 
     667              : } // detail
     668              : 
     669              : /** Return the number of elements in a buffer sequence.
     670              : */
     671              : template<class ConstBufferSequence>
     672              : std::size_t
     673           24 : length(ConstBufferSequence const& bs)
     674              : {
     675           44 :     return detail::length_impl(
     676           24 :         buffers::begin(bs), buffers::end(bs), 0);
     677              : }
     678              : 
     679              : } // buffers
     680              : } // boost
     681              : 
     682              : #endif
        

Generated by: LCOV version 2.1