100.00% Lines (22/22) 100.00% Functions (3/3)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
  3 + // Copyright (c) 2026 Michael Vandeberg
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // 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   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
8   // 9   //
9   10  
10   #ifndef BOOST_COROSIO_DETAIL_BUFFER_PARAM_HPP 11   #ifndef BOOST_COROSIO_DETAIL_BUFFER_PARAM_HPP
11   #define BOOST_COROSIO_DETAIL_BUFFER_PARAM_HPP 12   #define BOOST_COROSIO_DETAIL_BUFFER_PARAM_HPP
12   13  
13   #include <boost/corosio/detail/config.hpp> 14   #include <boost/corosio/detail/config.hpp>
14   #include <boost/capy/buffers.hpp> 15   #include <boost/capy/buffers.hpp>
15   16  
16   #include <cstddef> 17   #include <cstddef>
17   18  
18   namespace boost::corosio { 19   namespace boost::corosio {
19   20  
20   /** A type-erased buffer sequence for I/O system call boundaries. 21   /** A type-erased buffer sequence for I/O system call boundaries.
21   22  
22   This class enables I/O objects to accept any buffer sequence type 23   This class enables I/O objects to accept any buffer sequence type
23   across a virtual function boundary, while preserving the caller's 24   across a virtual function boundary, while preserving the caller's
24   typed buffer sequence at the call site. The implementation can 25   typed buffer sequence at the call site. The implementation can
25   then unroll the type-erased sequence into platform-native 26   then unroll the type-erased sequence into platform-native
26   structures (e.g., `iovec` on POSIX, `WSABUF` on Windows) for the 27   structures (e.g., `iovec` on POSIX, `WSABUF` on Windows) for the
27   actual system call. 28   actual system call.
28   29  
29   @par Purpose 30   @par Purpose
30   31  
31   When building coroutine-based I/O abstractions, a common pattern 32   When building coroutine-based I/O abstractions, a common pattern
32   emerges: a templated awaitable captures the caller's buffer 33   emerges: a templated awaitable captures the caller's buffer
33   sequence, and at `await_suspend` time, must pass it across a 34   sequence, and at `await_suspend` time, must pass it across a
34   virtual interface to the I/O implementation. This class solves 35   virtual interface to the I/O implementation. This class solves
35   the type-erasure problem at that boundary without heap allocation. 36   the type-erasure problem at that boundary without heap allocation.
36   37  
37   @par Restricted Use Case 38   @par Restricted Use Case
38   39  
39   This is NOT a general-purpose composable abstraction. It exists 40   This is NOT a general-purpose composable abstraction. It exists
40   solely for the final step in a coroutine I/O call chain where: 41   solely for the final step in a coroutine I/O call chain where:
41   42  
42   @li A templated awaitable captures the caller's buffer sequence 43   @li A templated awaitable captures the caller's buffer sequence
43   @li The awaitable's `await_suspend` passes buffers across a 44   @li The awaitable's `await_suspend` passes buffers across a
44   virtual interface to an I/O object implementation 45   virtual interface to an I/O object implementation
45   @li The implementation immediately unrolls the buffers into 46   @li The implementation immediately unrolls the buffers into
46   platform-native structures for the system call 47   platform-native structures for the system call
47   48  
48   @par Lifetime Model 49   @par Lifetime Model
49   50  
50   The safety of this class depends entirely on coroutine parameter 51   The safety of this class depends entirely on coroutine parameter
51   lifetime extension. When a coroutine is suspended, parameters 52   lifetime extension. When a coroutine is suspended, parameters
52   passed to the awaitable remain valid until the coroutine resumes 53   passed to the awaitable remain valid until the coroutine resumes
53   or is destroyed. This class exploits that guarantee by holding 54   or is destroyed. This class exploits that guarantee by holding
54   only a pointer to the caller's buffer sequence. 55   only a pointer to the caller's buffer sequence.
55   56  
56   The referenced buffer sequence is valid ONLY while the calling 57   The referenced buffer sequence is valid ONLY while the calling
57   coroutine remains suspended at the exact suspension point where 58   coroutine remains suspended at the exact suspension point where
58   `buffer_param` was created. Once the coroutine resumes, 59   `buffer_param` was created. Once the coroutine resumes,
59   returns, or is destroyed, all referenced data becomes invalid. 60   returns, or is destroyed, all referenced data becomes invalid.
60   61  
61   @par Const Buffer Handling 62   @par Const Buffer Handling
62   63  
63   This class accepts both `ConstBufferSequence` and 64   This class accepts both `ConstBufferSequence` and
64   `MutableBufferSequence` types. However, `copy_to` always produces 65   `MutableBufferSequence` types. However, `copy_to` always produces
65   `mutable_buffer` descriptors, casting away constness for const 66   `mutable_buffer` descriptors, casting away constness for const
66   buffer sequences. This design matches platform I/O structures 67   buffer sequences. This design matches platform I/O structures
67   (`iovec`, `WSABUF`) which use non-const pointers regardless of 68   (`iovec`, `WSABUF`) which use non-const pointers regardless of
68   the operation direction. 69   the operation direction.
69   70  
70   @warning The caller is responsible for ensuring the type system 71   @warning The caller is responsible for ensuring the type system
71   is not violated. When the original buffer sequence was const 72   is not violated. When the original buffer sequence was const
72   (e.g., for a write operation), the implementation MUST NOT write 73   (e.g., for a write operation), the implementation MUST NOT write
73   to the buffers obtained from `copy_to`. The const-cast exists 74   to the buffers obtained from `copy_to`. The const-cast exists
74   solely to provide a uniform interface for platform I/O calls. 75   solely to provide a uniform interface for platform I/O calls.
75   76  
  77 + @note Do NOT `reinterpret_cast` the `mutable_buffer` array to
  78 + `iovec*`/`WSABUF*` and pass it to the OS, even when the layouts
  79 + match: no object of the target type exists in that storage, so the
  80 + access is undefined behavior (and `mutable_buffer` is not an
  81 + implicit-lifetime type, so `std::start_lifetime_as_array` cannot
  82 + rescue it). Copy field by field into a real platform array instead.
  83 +
76   @code 84   @code
77   // For write operations (const buffers): 85   // For write operations (const buffers):
78   void submit_write(buffer_param p) 86   void submit_write(buffer_param p)
79   { 87   {
80   capy::mutable_buffer bufs[8]; 88   capy::mutable_buffer bufs[8];
81   auto n = p.copy_to(bufs, 8); 89   auto n = p.copy_to(bufs, 8);
82 - // bufs[] may reference const data - DO NOT WRITE 90 + iovec iov[8];
83 - writev(fd, reinterpret_cast<iovec*>(bufs), n); // OK: read-only 91 + for (std::size_t i = 0; i < n; ++i)
  92 + {
  93 + // bufs[] may reference const data - DO NOT WRITE through iov
  94 + iov[i].iov_base = bufs[i].data();
  95 + iov[i].iov_len = bufs[i].size();
  96 + }
  97 + writev(fd, iov, n); // read-only
84   } 98   }
85   99  
86   // For read operations (mutable buffers): 100   // For read operations (mutable buffers):
87   void submit_read(buffer_param p) 101   void submit_read(buffer_param p)
88   { 102   {
89   capy::mutable_buffer bufs[8]; 103   capy::mutable_buffer bufs[8];
90   auto n = p.copy_to(bufs, 8); 104   auto n = p.copy_to(bufs, 8);
91 - // bufs[] references mutable data - safe to write 105 + iovec iov[8];
92 - readv(fd, reinterpret_cast<iovec*>(bufs), n); // OK: writing 106 + for (std::size_t i = 0; i < n; ++i)
  107 + {
  108 + // bufs[] references mutable data - safe to write
  109 + iov[i].iov_base = bufs[i].data();
  110 + iov[i].iov_len = bufs[i].size();
  111 + }
  112 + readv(fd, iov, n); // writing
93   } 113   }
94   @endcode 114   @endcode
95   115  
96   @par Correct Usage 116   @par Correct Usage
97   117  
98   The implementation receiving `buffer_param` MUST: 118   The implementation receiving `buffer_param` MUST:
99   119  
100   @li Call `copy_to` immediately upon receiving the parameter 120   @li Call `copy_to` immediately upon receiving the parameter
101   @li Use the unrolled buffer descriptors for the I/O operation 121   @li Use the unrolled buffer descriptors for the I/O operation
102   @li Never store the `buffer_param` object itself 122   @li Never store the `buffer_param` object itself
103   @li Never store pointers obtained from `copy_to` beyond the 123   @li Never store pointers obtained from `copy_to` beyond the
104   immediate I/O operation 124   immediate I/O operation
105   125  
106   @par Example: Correct Usage 126   @par Example: Correct Usage
107   127  
108   @code 128   @code
109   // Templated awaitable at the call site 129   // Templated awaitable at the call site
110   template<class Buffers> 130   template<class Buffers>
111   struct write_awaitable 131   struct write_awaitable
112   { 132   {
113   Buffers bufs; 133   Buffers bufs;
114   io_stream* stream; 134   io_stream* stream;
115   135  
116   bool await_ready() { return false; } 136   bool await_ready() { return false; }
117   137  
118   void await_suspend(std::coroutine_handle<> h) 138   void await_suspend(std::coroutine_handle<> h)
119   { 139   {
120   // CORRECT: Pass to virtual interface while suspended. 140   // CORRECT: Pass to virtual interface while suspended.
121   // The buffer sequence 'bufs' remains valid because 141   // The buffer sequence 'bufs' remains valid because
122   // coroutine parameters live until resumption. 142   // coroutine parameters live until resumption.
123   stream->async_write_some_impl(bufs, h); 143   stream->async_write_some_impl(bufs, h);
124   } 144   }
125   145  
126   io_result await_resume() { return stream->get_result(); } 146   io_result await_resume() { return stream->get_result(); }
127   }; 147   };
128   148  
129   // Virtual implementation - unrolls immediately 149   // Virtual implementation - unrolls immediately
130   void stream_impl::async_write_some_impl( 150   void stream_impl::async_write_some_impl(
131   buffer_param p, 151   buffer_param p,
132   std::coroutine_handle<> h) 152   std::coroutine_handle<> h)
133   { 153   {
134 - // CORRECT: Unroll immediately into platform structure 154 + // CORRECT: Unroll immediately into platform structure.
  155 + // Copy field by field; do NOT reinterpret_cast the
  156 + // mutable_buffer array to iovec* (see the @note above).
  157 + capy::mutable_buffer bufs[16];
  158 + std::size_t n = p.copy_to(bufs, 16);
135   iovec vecs[16]; 159   iovec vecs[16];
136 - std::size_t n = p.copy_to( 160 + for (std::size_t i = 0; i < n; ++i)
137 - reinterpret_cast<capy::mutable_buffer*>(vecs), 16); 161 + {
  162 + vecs[i].iov_base = bufs[i].data();
  163 + vecs[i].iov_len = bufs[i].size();
  164 + }
138   165  
139   // CORRECT: Use unrolled buffers for system call now 166   // CORRECT: Use unrolled buffers for system call now
140   submit_to_io_uring(vecs, n, h); 167   submit_to_io_uring(vecs, n, h);
141   168  
142   // After this function returns, 'p' must not be used again. 169   // After this function returns, 'p' must not be used again.
143   // The iovec array is safe because it contains copies of 170   // The iovec array is safe because it contains copies of
144   // the pointer/size pairs, not references to 'p'. 171   // the pointer/size pairs, not references to 'p'.
145   } 172   }
146   @endcode 173   @endcode
147   174  
148   @par UNSAFE USAGE: Storing buffer_param 175   @par UNSAFE USAGE: Storing buffer_param
149   176  
150   @warning Never store `buffer_param` for later use. 177   @warning Never store `buffer_param` for later use.
151   178  
152   @code 179   @code
153   class broken_stream 180   class broken_stream
154   { 181   {
155   buffer_param saved_param_; // UNSAFE: member storage 182   buffer_param saved_param_; // UNSAFE: member storage
156   183  
157   void async_write_impl(buffer_param p, ...) 184   void async_write_impl(buffer_param p, ...)
158   { 185   {
159   saved_param_ = p; // UNSAFE: storing for later 186   saved_param_ = p; // UNSAFE: storing for later
160   schedule_write_later(); 187   schedule_write_later();
161   } 188   }
162   189  
163   void do_write_later() 190   void do_write_later()
164   { 191   {
165   // UNSAFE: The calling coroutine may have resumed 192   // UNSAFE: The calling coroutine may have resumed
166   // or been destroyed. saved_param_ now references 193   // or been destroyed. saved_param_ now references
167   // invalid memory! 194   // invalid memory!
168   capy::mutable_buffer bufs[8]; 195   capy::mutable_buffer bufs[8];
169   saved_param_.copy_to(bufs, 8); // UNDEFINED BEHAVIOR 196   saved_param_.copy_to(bufs, 8); // UNDEFINED BEHAVIOR
170   } 197   }
171   }; 198   };
172   @endcode 199   @endcode
173   200  
174   @par UNSAFE USAGE: Storing Unrolled Pointers 201   @par UNSAFE USAGE: Storing Unrolled Pointers
175   202  
176   @warning The pointers obtained from `copy_to` point into the 203   @warning The pointers obtained from `copy_to` point into the
177   caller's buffer sequence. They become invalid when the caller 204   caller's buffer sequence. They become invalid when the caller
178   resumes. 205   resumes.
179   206  
180   @code 207   @code
181   class broken_stream 208   class broken_stream
182   { 209   {
183   capy::mutable_buffer saved_bufs_[8]; // UNSAFE 210   capy::mutable_buffer saved_bufs_[8]; // UNSAFE
184   std::size_t saved_count_; 211   std::size_t saved_count_;
185   212  
186   void async_write_impl(buffer_param p, ...) 213   void async_write_impl(buffer_param p, ...)
187   { 214   {
188   // This copies pointer/size pairs into saved_bufs_ 215   // This copies pointer/size pairs into saved_bufs_
189   saved_count_ = p.copy_to(saved_bufs_, 8); 216   saved_count_ = p.copy_to(saved_bufs_, 8);
190   217  
191   // UNSAFE: scheduling for later while storing the 218   // UNSAFE: scheduling for later while storing the
192   // buffer descriptors. The pointers in saved_bufs_ 219   // buffer descriptors. The pointers in saved_bufs_
193   // will dangle when the caller resumes! 220   // will dangle when the caller resumes!
194   schedule_for_later(); 221   schedule_for_later();
195   } 222   }
196   223  
197   void later() 224   void later()
198   { 225   {
199   // UNSAFE: saved_bufs_ contains dangling pointers 226   // UNSAFE: saved_bufs_ contains dangling pointers
200   for(std::size_t i = 0; i < saved_count_; ++i) 227   for(std::size_t i = 0; i < saved_count_; ++i)
201   write(fd_, saved_bufs_[i].data(), ...); // UB 228   write(fd_, saved_bufs_[i].data(), ...); // UB
202   } 229   }
203   }; 230   };
204   @endcode 231   @endcode
205   232  
206   @par UNSAFE USAGE: Using Outside a Coroutine 233   @par UNSAFE USAGE: Using Outside a Coroutine
207   234  
208   @warning This class relies on coroutine lifetime semantics. 235   @warning This class relies on coroutine lifetime semantics.
209   Using it with callbacks or non-coroutine async patterns is 236   Using it with callbacks or non-coroutine async patterns is
210   undefined behavior. 237   undefined behavior.
211   238  
212   @code 239   @code
213   // UNSAFE: No coroutine lifetime guarantee 240   // UNSAFE: No coroutine lifetime guarantee
214   void bad_callback_pattern(std::vector<char>& data) 241   void bad_callback_pattern(std::vector<char>& data)
215   { 242   {
216   capy::mutable_buffer buf(data.data(), data.size()); 243   capy::mutable_buffer buf(data.data(), data.size());
217   244  
218   // UNSAFE: In a callback model, 'buf' may go out of scope 245   // UNSAFE: In a callback model, 'buf' may go out of scope
219   // before the callback fires. There is no coroutine 246   // before the callback fires. There is no coroutine
220   // suspension to extend the lifetime. 247   // suspension to extend the lifetime.
221   stream.async_write(buf, [](error_code ec) { 248   stream.async_write(buf, [](error_code ec) {
222   // 'buf' is already destroyed! 249   // 'buf' is already destroyed!
223   }); 250   });
224   } 251   }
225   @endcode 252   @endcode
226   253  
227   @par UNSAFE USAGE: Passing to Another Coroutine 254   @par UNSAFE USAGE: Passing to Another Coroutine
228   255  
229   @warning Do not pass `buffer_param` to a different coroutine 256   @warning Do not pass `buffer_param` to a different coroutine
230   or spawn a new coroutine that captures it. 257   or spawn a new coroutine that captures it.
231   258  
232   @code 259   @code
233   void broken_impl(buffer_param p, std::coroutine_handle<> h) 260   void broken_impl(buffer_param p, std::coroutine_handle<> h)
234   { 261   {
235   // UNSAFE: Spawning a new coroutine that captures 'p'. 262   // UNSAFE: Spawning a new coroutine that captures 'p'.
236   // The original coroutine may resume before this new 263   // The original coroutine may resume before this new
237   // coroutine uses 'p'. 264   // coroutine uses 'p'.
238   co_spawn([p]() -> task<void> { 265   co_spawn([p]() -> task<void> {
239   capy::mutable_buffer bufs[8]; 266   capy::mutable_buffer bufs[8];
240   p.copy_to(bufs, 8); // UNSAFE: original caller may 267   p.copy_to(bufs, 8); // UNSAFE: original caller may
241   // have resumed already! 268   // have resumed already!
242   co_return; 269   co_return;
243   }); 270   });
244   } 271   }
245   @endcode 272   @endcode
246   273  
247   @par UNSAFE USAGE: Multiple Virtual Hops 274   @par UNSAFE USAGE: Multiple Virtual Hops
248   275  
249   @warning Minimize indirection. Each virtual call that passes 276   @warning Minimize indirection. Each virtual call that passes
250   `buffer_param` without immediately unrolling it increases 277   `buffer_param` without immediately unrolling it increases
251   the risk of misuse. 278   the risk of misuse.
252   279  
253   @code 280   @code
254   // Risky: multiple hops before unrolling 281   // Risky: multiple hops before unrolling
255   void layer1(buffer_param p) { 282   void layer1(buffer_param p) {
256   layer2(p); // Still haven't unrolled... 283   layer2(p); // Still haven't unrolled...
257   } 284   }
258   void layer2(buffer_param p) { 285   void layer2(buffer_param p) {
259   layer3(p); // Still haven't unrolled... 286   layer3(p); // Still haven't unrolled...
260   } 287   }
261   void layer3(buffer_param p) { 288   void layer3(buffer_param p) {
262   // Finally unrolling, but the chain is fragile. 289   // Finally unrolling, but the chain is fragile.
263   // Any intermediate layer storing 'p' breaks everything. 290   // Any intermediate layer storing 'p' breaks everything.
264   } 291   }
265   @endcode 292   @endcode
266   293  
267   @par UNSAFE USAGE: Fire-and-Forget Operations 294   @par UNSAFE USAGE: Fire-and-Forget Operations
268   295  
269   @warning Do not use with detached or fire-and-forget async 296   @warning Do not use with detached or fire-and-forget async
270   operations where there is no guarantee the caller remains 297   operations where there is no guarantee the caller remains
271   suspended. 298   suspended.
272   299  
273   @code 300   @code
274   task<void> caller() 301   task<void> caller()
275   { 302   {
276   char buf[1024]; 303   char buf[1024];
277   // UNSAFE: If async_write is fire-and-forget (doesn't 304   // UNSAFE: If async_write is fire-and-forget (doesn't
278   // actually suspend the caller), 'buf' may be destroyed 305   // actually suspend the caller), 'buf' may be destroyed
279   // before the I/O completes. 306   // before the I/O completes.
280   stream.async_write_detached(capy::mutable_buffer(buf, 1024)); 307   stream.async_write_detached(capy::mutable_buffer(buf, 1024));
281   // Returns immediately - 'buf' goes out of scope! 308   // Returns immediately - 'buf' goes out of scope!
282   } 309   }
283   @endcode 310   @endcode
284   311  
285   @par Passing Convention 312   @par Passing Convention
286   313  
287   Pass by value. The class contains only two pointers (16 bytes 314   Pass by value. The class contains only two pointers (16 bytes
288   on 64-bit systems), making copies trivial and clearly 315   on 64-bit systems), making copies trivial and clearly
289   communicating the lightweight, transient nature of this type. 316   communicating the lightweight, transient nature of this type.
290   317  
291   @code 318   @code
292   // Preferred: pass by value 319   // Preferred: pass by value
293   void process(buffer_param buffers); 320   void process(buffer_param buffers);
294   321  
295   // Also acceptable: pass by const reference 322   // Also acceptable: pass by const reference
296   void process(buffer_param const& buffers); 323   void process(buffer_param const& buffers);
297   @endcode 324   @endcode
298   325  
299   @see capy::ConstBufferSequence, capy::MutableBufferSequence 326   @see capy::ConstBufferSequence, capy::MutableBufferSequence
300   */ 327   */
301   class buffer_param 328   class buffer_param
302   { 329   {
303   public: 330   public:
304   /** Construct from a const buffer sequence. 331   /** Construct from a const buffer sequence.
305   332  
306   @param bs The buffer sequence to adapt. 333   @param bs The buffer sequence to adapt.
307   */ 334   */
308   template<capy::ConstBufferSequence BS> 335   template<capy::ConstBufferSequence BS>
HITCBC 309   438953 buffer_param(BS const& bs) noexcept : bs_(&bs) 336   413367 buffer_param(BS const& bs) noexcept : bs_(&bs)
HITCBC 310   438953 , fn_(&copy_impl<BS>) 337   413367 , fn_(&copy_impl<BS>)
311   { 338   {
HITCBC 312   438953 } 339   413367 }
313   340  
314   /** Fill an array with buffers from the sequence. 341   /** Fill an array with buffers from the sequence.
315   342  
316   Copies buffer descriptors from the sequence into the 343   Copies buffer descriptors from the sequence into the
317   destination array, skipping any zero-size buffers. 344   destination array, skipping any zero-size buffers.
318   This ensures the output contains only buffers with 345   This ensures the output contains only buffers with
319   actual data, suitable for direct use with system calls. 346   actual data, suitable for direct use with system calls.
320   347  
321   @param dest Pointer to array of mutable buffer descriptors. 348   @param dest Pointer to array of mutable buffer descriptors.
322   @param n Maximum number of buffers to copy. 349   @param n Maximum number of buffers to copy.
323   350  
324   @return The number of non-zero buffers copied. 351   @return The number of non-zero buffers copied.
325   */ 352   */
326   std::size_t 353   std::size_t
HITCBC 327   438953 copy_to(capy::mutable_buffer* dest, std::size_t n) const noexcept 354   413367 copy_to(capy::mutable_buffer* dest, std::size_t n) const noexcept
328   { 355   {
HITCBC 329   438953 return fn_(bs_, dest, n); 356   413367 return fn_(bs_, dest, n);
330   } 357   }
331   358  
332   private: 359   private:
333   template<capy::ConstBufferSequence BS> 360   template<capy::ConstBufferSequence BS>
334   static std::size_t 361   static std::size_t
HITCBC 335   438953 copy_impl(void const* p, capy::mutable_buffer* dest, std::size_t n) 362   413367 copy_impl(void const* p, capy::mutable_buffer* dest, std::size_t n)
336   { 363   {
HITCBC 337   438953 auto const& bs = *static_cast<BS const*>(p); 364   413367 auto const& bs = *static_cast<BS const*>(p);
HITCBC 338   438953 auto it = capy::begin(bs); 365   413367 auto it = capy::begin(bs);
HITCBC 339   438953 auto const end_it = capy::end(bs); 366   413367 auto const end_it = capy::end(bs);
340   367  
HITCBC 341   438953 std::size_t i = 0; 368   413367 std::size_t i = 0;
342   if constexpr (capy::MutableBufferSequence<BS>) 369   if constexpr (capy::MutableBufferSequence<BS>)
343   { 370   {
HITCBC 344   439812 for (; it != end_it && i < n; ++it) 371   414226 for (; it != end_it && i < n; ++it)
345   { 372   {
HITCBC 346   219911 capy::mutable_buffer buf(*it); 373   207118 capy::mutable_buffer buf(*it);
HITCBC 347   219911 if (buf.size() == 0) 374   207118 if (buf.size() == 0)
HITCBC 348   17 continue; 375   17 continue;
HITCBC 349   219894 dest[i++] = buf; 376   207101 dest[i++] = buf;
350   } 377   }
351   } 378   }
352   else 379   else
353   { 380   {
HITCBC 354   438126 for (; it != end_it && i < n; ++it) 381   412540 for (; it != end_it && i < n; ++it)
355   { 382   {
HITCBC 356   219074 capy::const_buffer buf(*it); 383   206281 capy::const_buffer buf(*it);
HITCBC 357   219074 if (buf.size() == 0) 384   206281 if (buf.size() == 0)
HITCBC 358   24 continue; 385   24 continue;
HITCBC 359   438100 dest[i++] = capy::mutable_buffer( 386   412514 dest[i++] = capy::mutable_buffer(
HITCBC 360   219050 const_cast<char*>(static_cast<char const*>(buf.data())), 387   206257 const_cast<char*>(static_cast<char const*>(buf.data())),
361   buf.size()); 388   buf.size());
362   } 389   }
363   } 390   }
HITCBC 364   438953 return i; 391   413367 return i;
365   } 392   }
366   393  
367   using fn_t = 394   using fn_t =
368   std::size_t (*)(void const*, capy::mutable_buffer*, std::size_t); 395   std::size_t (*)(void const*, capy::mutable_buffer*, std::size_t);
369   396  
370   void const* bs_; 397   void const* bs_;
371   fn_t fn_; 398   fn_t fn_;
372   }; 399   };
373   400  
374   } // namespace boost::corosio 401   } // namespace boost::corosio
375   402  
376   #endif 403   #endif