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