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