90.41% Lines (66/73) 91.30% Functions (21/23)
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/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_IO_WRITE_NOW_HPP 10   #ifndef BOOST_CAPY_IO_WRITE_NOW_HPP
11   #define BOOST_CAPY_IO_WRITE_NOW_HPP 11   #define BOOST_CAPY_IO_WRITE_NOW_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/detail/await_suspend_helper.hpp> 14   #include <boost/capy/detail/await_suspend_helper.hpp>
15   #include <boost/capy/buffers.hpp> 15   #include <boost/capy/buffers.hpp>
16   #include <boost/capy/buffers/consuming_buffers.hpp> 16   #include <boost/capy/buffers/consuming_buffers.hpp>
17   #include <boost/capy/concept/io_awaitable.hpp> 17   #include <boost/capy/concept/io_awaitable.hpp>
18   #include <boost/capy/concept/write_stream.hpp> 18   #include <boost/capy/concept/write_stream.hpp>
19   #include <coroutine> 19   #include <coroutine>
20   #include <boost/capy/ex/executor_ref.hpp> 20   #include <boost/capy/ex/executor_ref.hpp>
21   #include <boost/capy/ex/io_env.hpp> 21   #include <boost/capy/ex/io_env.hpp>
22   #include <boost/capy/io_result.hpp> 22   #include <boost/capy/io_result.hpp>
23   23  
24   #include <cstddef> 24   #include <cstddef>
25   #include <exception> 25   #include <exception>
26   #include <new> 26   #include <new>
27   #include <stop_token> 27   #include <stop_token>
28   #include <utility> 28   #include <utility>
29   29  
30   #ifndef BOOST_CAPY_WRITE_NOW_WORKAROUND 30   #ifndef BOOST_CAPY_WRITE_NOW_WORKAROUND
31   # if defined(__GNUC__) && !defined(__clang__) 31   # if defined(__GNUC__) && !defined(__clang__)
32   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 1 32   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 1
33   # else 33   # else
34   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 0 34   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 0
35   # endif 35   # endif
36   #endif 36   #endif
37   37  
38   namespace boost { 38   namespace boost {
39   namespace capy { 39   namespace capy {
40   40  
41   /** Eagerly writes complete buffer sequences with frame caching. 41   /** Eagerly writes complete buffer sequences with frame caching.
42   42  
43   This class wraps a @ref WriteStream and provides an `operator()` 43   This class wraps a @ref WriteStream and provides an `operator()`
44   that writes an entire buffer sequence, attempting to complete 44   that writes an entire buffer sequence, attempting to complete
45   synchronously. If every `write_some` completes without suspending, 45   synchronously. If every `write_some` completes without suspending,
46   the entire operation finishes in `await_ready` with no coroutine 46   the entire operation finishes in `await_ready` with no coroutine
47   suspension. 47   suspension.
48   48  
49   The class maintains a one-element coroutine frame cache. After 49   The class maintains a one-element coroutine frame cache. After
50   the first call, subsequent calls reuse the cached frame memory, 50   the first call, subsequent calls reuse the cached frame memory,
51   avoiding repeated allocation for the internal coroutine. 51   avoiding repeated allocation for the internal coroutine.
52   52  
53   @tparam Stream The stream type, must satisfy @ref WriteStream. 53   @tparam Stream The stream type, must satisfy @ref WriteStream.
54   54  
55   @par Thread Safety 55   @par Thread Safety
56   Distinct objects: Safe. 56   Distinct objects: Safe.
57   Shared objects: Unsafe. 57   Shared objects: Unsafe.
58   58  
59   @par Preconditions 59   @par Preconditions
60   Only one operation may be outstanding at a time. A new call to 60   Only one operation may be outstanding at a time. A new call to
61   `operator()` must not be made until the previous operation has 61   `operator()` must not be made until the previous operation has
62   completed (i.e., the returned awaitable has been fully consumed). 62   completed (i.e., the returned awaitable has been fully consumed).
63   63  
64   @par Example 64   @par Example
65   65  
66   @code 66   @code
67   template< WriteStream Stream > 67   template< WriteStream Stream >
68   task<> send_messages( Stream& stream ) 68   task<> send_messages( Stream& stream )
69   { 69   {
70   write_now wn( stream ); 70   write_now wn( stream );
71   auto [ec1, n1] = co_await wn( make_buffer( "hello" ) ); 71   auto [ec1, n1] = co_await wn( make_buffer( "hello" ) );
72   if( ec1 ) 72   if( ec1 )
73   detail::throw_system_error( ec1 ); 73   detail::throw_system_error( ec1 );
74   auto [ec2, n2] = co_await wn( make_buffer( "world" ) ); 74   auto [ec2, n2] = co_await wn( make_buffer( "world" ) );
75   if( ec2 ) 75   if( ec2 )
76   detail::throw_system_error( ec2 ); 76   detail::throw_system_error( ec2 );
77   } 77   }
78   @endcode 78   @endcode
79   79  
80   @see write, write_some, WriteStream, ConstBufferSequence 80   @see write, write_some, WriteStream, ConstBufferSequence
81   */ 81   */
82   template<class Stream> 82   template<class Stream>
83   requires WriteStream<Stream> 83   requires WriteStream<Stream>
84   class write_now 84   class write_now
85   { 85   {
86   Stream& stream_; 86   Stream& stream_;
87   void* cached_frame_ = nullptr; 87   void* cached_frame_ = nullptr;
88   std::size_t cached_size_ = 0; 88   std::size_t cached_size_ = 0;
89   89  
90   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 90   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
91   op_type 91   op_type
92   { 92   {
93   struct promise_type 93   struct promise_type
94   { 94   {
95   io_result<std::size_t> result_; 95   io_result<std::size_t> result_;
96   std::exception_ptr ep_; 96   std::exception_ptr ep_;
97   std::coroutine_handle<> cont_{nullptr}; 97   std::coroutine_handle<> cont_{nullptr};
98   io_env const* env_ = nullptr; 98   io_env const* env_ = nullptr;
99   bool done_ = false; 99   bool done_ = false;
100   100  
HITCBC 101   68 op_type get_return_object() 101   68 op_type get_return_object()
102   { 102   {
103   return op_type{ 103   return op_type{
104   std::coroutine_handle< 104   std::coroutine_handle<
HITCBC 105   68 promise_type>::from_promise(*this)}; 105   68 promise_type>::from_promise(*this)};
106   } 106   }
107   107  
HITCBC 108   68 auto initial_suspend() noexcept 108   68 auto initial_suspend() noexcept
109   { 109   {
110   #if BOOST_CAPY_WRITE_NOW_WORKAROUND 110   #if BOOST_CAPY_WRITE_NOW_WORKAROUND
HITCBC 111   68 return std::suspend_always{}; 111   68 return std::suspend_always{};
112   #else 112   #else
113   return std::suspend_never{}; 113   return std::suspend_never{};
114   #endif 114   #endif
115   } 115   }
116   116  
HITCBC 117   68 auto final_suspend() noexcept 117   68 auto final_suspend() noexcept
118   { 118   {
119   struct awaiter 119   struct awaiter
120   { 120   {
121   promise_type* p_; 121   promise_type* p_;
122   122  
HITCBC 123   68 bool await_ready() const noexcept 123   68 bool await_ready() const noexcept
124   { 124   {
HITCBC 125   68 return false; 125   68 return false;
126   } 126   }
127   127  
HITCBC 128   68 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 128   68 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
129   { 129   {
HITCBC 130   68 p_->done_ = true; 130   68 p_->done_ = true;
HITCBC 131   68 if(!p_->cont_) 131   68 if(!p_->cont_)
MISUBC 132   return std::noop_coroutine(); 132   return std::noop_coroutine();
HITCBC 133   68 return p_->cont_; 133   68 return p_->cont_;
134   } 134   }
135   135  
MISUBC 136   void await_resume() const noexcept 136   void await_resume() const noexcept
137   { 137   {
MISUBC 138   } 138   }
139   }; 139   };
HITCBC 140   68 return awaiter{this}; 140   68 return awaiter{this};
141   } 141   }
142   142  
HITCBC 143   46 void return_value( 143   46 void return_value(
144   io_result<std::size_t> r) noexcept 144   io_result<std::size_t> r) noexcept
145   { 145   {
HITCBC 146   46 result_ = r; 146   46 result_ = r;
HITCBC 147   46 } 147   46 }
148   148  
HITCBC 149   22 void unhandled_exception() 149   22 void unhandled_exception()
150   { 150   {
HITCBC 151   22 ep_ = std::current_exception(); 151   22 ep_ = std::current_exception();
HITCBC 152   22 } 152   22 }
153   153  
154   std::suspend_always yield_value(int) noexcept 154   std::suspend_always yield_value(int) noexcept
155   { 155   {
156   return {}; 156   return {};
157   } 157   }
158   158  
159   template<class A> 159   template<class A>
HITCBC 160   84 auto await_transform(A&& a) 160   84 auto await_transform(A&& a)
161   { 161   {
162   using decayed = std::decay_t<A>; 162   using decayed = std::decay_t<A>;
163   if constexpr (IoAwaitable<decayed>) 163   if constexpr (IoAwaitable<decayed>)
164   { 164   {
165   struct wrapper 165   struct wrapper
166   { 166   {
167   decayed inner_; 167   decayed inner_;
168   promise_type* p_; 168   promise_type* p_;
169   169  
HITCBC 170   84 bool await_ready() 170   84 bool await_ready()
171   { 171   {
HITCBC 172   84 return inner_.await_ready(); 172   84 return inner_.await_ready();
173   } 173   }
174   174  
MISUBC 175   std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) 175   std::coroutine_handle<> await_suspend(std::coroutine_handle<> h)
176   { 176   {
MISUBC 177   return detail::call_await_suspend( 177   return detail::call_await_suspend(
178   &inner_, h, 178   &inner_, h,
MISUBC 179   p_->env_); 179   p_->env_);
180   } 180   }
181   181  
HITCBC 182   84 decltype(auto) await_resume() 182   84 decltype(auto) await_resume()
183   { 183   {
HITCBC 184   84 return inner_.await_resume(); 184   84 return inner_.await_resume();
185   } 185   }
186   }; 186   };
187   return wrapper{ 187   return wrapper{
HITCBC 188   84 std::forward<A>(a), this}; 188   84 std::forward<A>(a), this};
189   } 189   }
190   else 190   else
191   { 191   {
192   return std::forward<A>(a); 192   return std::forward<A>(a);
193   } 193   }
194   } 194   }
195   195  
196   static void* 196   static void*
HITCBC 197   68 operator new( 197   68 operator new(
198   std::size_t size, 198   std::size_t size,
199   write_now& self, 199   write_now& self,
200   auto&) 200   auto&)
201   { 201   {
HITCBC 202   68 if(self.cached_frame_ && 202   68 if(self.cached_frame_ &&
HITCBC 203   4 self.cached_size_ >= size) 203   4 self.cached_size_ >= size)
HITCBC 204   4 return self.cached_frame_; 204   4 return self.cached_frame_;
HITCBC 205   64 void* p = ::operator new(size); 205   64 void* p = ::operator new(size);
HITCBC 206   64 if(self.cached_frame_) 206   64 if(self.cached_frame_)
MISUBC 207   ::operator delete(self.cached_frame_); 207   ::operator delete(self.cached_frame_);
HITCBC 208   64 self.cached_frame_ = p; 208   64 self.cached_frame_ = p;
HITCBC 209   64 self.cached_size_ = size; 209   64 self.cached_size_ = size;
HITCBC 210   64 return p; 210   64 return p;
211   } 211   }
212   212  
213   static void 213   static void
HITCBC 214   68 operator delete(void*, std::size_t) noexcept 214   68 operator delete(void*, std::size_t) noexcept
215   { 215   {
HITCBC 216   68 } 216   68 }
217   }; 217   };
218   218  
219   std::coroutine_handle<promise_type> h_; 219   std::coroutine_handle<promise_type> h_;
220   220  
HITCBC 221   136 ~op_type() 221   136 ~op_type()
222   { 222   {
HITCBC 223   136 if(h_) 223   136 if(h_)
HITCBC 224   68 h_.destroy(); 224   68 h_.destroy();
HITCBC 225   136 } 225   136 }
226   226  
227   op_type(op_type const&) = delete; 227   op_type(op_type const&) = delete;
228   op_type& operator=(op_type const&) = delete; 228   op_type& operator=(op_type const&) = delete;
229   229  
HITCBC 230   68 op_type(op_type&& other) noexcept 230   68 op_type(op_type&& other) noexcept
HITCBC 231   68 : h_(std::exchange(other.h_, nullptr)) 231   68 : h_(std::exchange(other.h_, nullptr))
232   { 232   {
HITCBC 233   68 } 233   68 }
234   234  
235   op_type& operator=(op_type&&) = delete; 235   op_type& operator=(op_type&&) = delete;
236   236  
HITCBC 237   68 bool await_ready() const noexcept 237   68 bool await_ready() const noexcept
238   { 238   {
HITCBC 239   68 return h_.promise().done_; 239   68 return h_.promise().done_;
240   } 240   }
241   241  
HITCBC 242   68 std::coroutine_handle<> await_suspend( 242   68 std::coroutine_handle<> await_suspend(
243   std::coroutine_handle<> cont, 243   std::coroutine_handle<> cont,
244   io_env const* env) 244   io_env const* env)
245   { 245   {
HITCBC 246   68 auto& p = h_.promise(); 246   68 auto& p = h_.promise();
HITCBC 247   68 p.cont_ = cont; 247   68 p.cont_ = cont;
HITCBC 248   68 p.env_ = env; 248   68 p.env_ = env;
HITCBC 249   68 return h_; 249   68 return h_;
250   } 250   }
251   251  
HITCBC 252   68 io_result<std::size_t> await_resume() 252   68 io_result<std::size_t> await_resume()
253   { 253   {
HITCBC 254   68 auto& p = h_.promise(); 254   68 auto& p = h_.promise();
HITCBC 255   68 if(p.ep_) 255   68 if(p.ep_)
HITCBC 256   22 std::rethrow_exception(p.ep_); 256   22 std::rethrow_exception(p.ep_);
HITCBC 257   46 return p.result_; 257   46 return p.result_;
258   } 258   }
259   259  
260   private: 260   private:
HITCBC 261   68 explicit op_type( 261   68 explicit op_type(
262   std::coroutine_handle<promise_type> h) 262   std::coroutine_handle<promise_type> h)
HITCBC 263   68 : h_(h) 263   68 : h_(h)
264   { 264   {
HITCBC 265   68 } 265   68 }
266   }; 266   };
267   267  
268   public: 268   public:
269   /** Destructor. Frees the cached coroutine frame. */ 269   /** Destructor. Frees the cached coroutine frame. */
HITCBC 270   64 ~write_now() 270   64 ~write_now()
271   { 271   {
HITCBC 272   64 if(cached_frame_) 272   64 if(cached_frame_)
HITCBC 273   64 ::operator delete(cached_frame_); 273   64 ::operator delete(cached_frame_);
HITCBC 274   64 } 274   64 }
275   275  
276   /** Construct from a stream reference. 276   /** Construct from a stream reference.
277   277  
278   @param s The stream to write to. Must outlive this object. 278   @param s The stream to write to. Must outlive this object.
279   */ 279   */
280   explicit 280   explicit
HITCBC 281   64 write_now(Stream& s) noexcept 281   64 write_now(Stream& s) noexcept
HITCBC 282   64 : stream_(s) 282   64 : stream_(s)
283   { 283   {
HITCBC 284   64 } 284   64 }
285   285  
286   write_now(write_now const&) = delete; 286   write_now(write_now const&) = delete;
287   write_now& operator=(write_now const&) = delete; 287   write_now& operator=(write_now const&) = delete;
288   288  
289   /** Eagerly write the entire buffer sequence. 289   /** Eagerly write the entire buffer sequence.
290   290  
291   Writes data to the stream by calling `write_some` repeatedly 291   Writes data to the stream by calling `write_some` repeatedly
292   until the entire buffer sequence is written or an error 292   until the entire buffer sequence is written or an error
293   occurs. The operation attempts to complete synchronously: 293   occurs. The operation attempts to complete synchronously:
294   if every `write_some` completes without suspending, the 294   if every `write_some` completes without suspending, the
295   entire operation finishes in `await_ready`. 295   entire operation finishes in `await_ready`.
296   296  
297   When the fast path cannot complete, the coroutine suspends 297   When the fast path cannot complete, the coroutine suspends
298   and continues asynchronously. The internal coroutine frame 298   and continues asynchronously. The internal coroutine frame
299   is cached and reused across calls. 299   is cached and reused across calls.
300   300  
301   @param buffers The buffer sequence to write. Passed by 301   @param buffers The buffer sequence to write. Passed by
302   value to ensure the sequence lives in the coroutine 302   value to ensure the sequence lives in the coroutine
303   frame across suspension points. 303   frame across suspension points.
304   304  
305   @return An awaitable that await-returns `(error_code,std::size_t)`. 305   @return An awaitable that await-returns `(error_code,std::size_t)`.
306   On success, `n` equals `buffer_size(buffers)`. On 306   On success, `n` equals `buffer_size(buffers)`. On
307   error, `n` is the number of bytes written before the 307   error, `n` is the number of bytes written before the
308   error. Compare error codes to conditions: 308   error. Compare error codes to conditions:
309   @li `cond::canceled` - Operation was cancelled 309   @li `cond::canceled` - Operation was cancelled
310   @li `std::errc::broken_pipe` - Peer closed connection 310   @li `std::errc::broken_pipe` - Peer closed connection
311   311  
312   @par Example 312   @par Example
313   313  
314   @code 314   @code
315   write_now wn( stream ); 315   write_now wn( stream );
316   auto [ec, n] = co_await wn( make_buffer( body ) ); 316   auto [ec, n] = co_await wn( make_buffer( body ) );
317   if( ec ) 317   if( ec )
318   detail::throw_system_error( ec ); 318   detail::throw_system_error( ec );
319   @endcode 319   @endcode
320   320  
321   @see write, write_some, WriteStream 321   @see write, write_some, WriteStream
322   */ 322   */
323   // GCC falsely warns that the coroutine promise's 323   // GCC falsely warns that the coroutine promise's
324   // placement operator new(size_t, write_now&, auto&) 324   // placement operator new(size_t, write_now&, auto&)
325   // mismatches operator delete(void*, size_t). Per the 325   // mismatches operator delete(void*, size_t). Per the
326   // standard, coroutine deallocation lookup is separate. 326   // standard, coroutine deallocation lookup is separate.
327   #if defined(__GNUC__) && !defined(__clang__) 327   #if defined(__GNUC__) && !defined(__clang__)
328   #pragma GCC diagnostic push 328   #pragma GCC diagnostic push
329   #pragma GCC diagnostic ignored "-Wmismatched-new-delete" 329   #pragma GCC diagnostic ignored "-Wmismatched-new-delete"
330   #endif 330   #endif
331   331  
332   #if BOOST_CAPY_WRITE_NOW_WORKAROUND 332   #if BOOST_CAPY_WRITE_NOW_WORKAROUND
333   template<ConstBufferSequence Buffers> 333   template<ConstBufferSequence Buffers>
334   op_type 334   op_type
HITCBC 335   68 operator()(Buffers buffers) 335   68 operator()(Buffers buffers)
336   { 336   {
337   std::size_t const total_size = buffer_size(buffers); 337   std::size_t const total_size = buffer_size(buffers);
338   std::size_t total_written = 0; 338   std::size_t total_written = 0;
339   consuming_buffers cb(buffers); 339   consuming_buffers cb(buffers);
340   while(total_written < total_size) 340   while(total_written < total_size)
341   { 341   {
342   auto r = 342   auto r =
343   co_await stream_.write_some(cb); 343   co_await stream_.write_some(cb);
344   cb.consume(std::get<0>(r.values)); 344   cb.consume(std::get<0>(r.values));
345   total_written += std::get<0>(r.values); 345   total_written += std::get<0>(r.values);
346   if(r.ec) 346   if(r.ec)
347   co_return io_result<std::size_t>{ 347   co_return io_result<std::size_t>{
348   r.ec, total_written}; 348   r.ec, total_written};
349   } 349   }
350   co_return io_result<std::size_t>{ 350   co_return io_result<std::size_t>{
351   {}, total_written}; 351   {}, total_written};
HITCBC 352   136 } 352   136 }
353   #else 353   #else
354   template<ConstBufferSequence Buffers> 354   template<ConstBufferSequence Buffers>
355   op_type 355   op_type
356   operator()(Buffers buffers) 356   operator()(Buffers buffers)
357   { 357   {
358   std::size_t const total_size = buffer_size(buffers); 358   std::size_t const total_size = buffer_size(buffers);
359   std::size_t total_written = 0; 359   std::size_t total_written = 0;
360   360  
361   // GCC ICE in expand_expr_real_1 (expr.cc:11376) 361   // GCC ICE in expand_expr_real_1 (expr.cc:11376)
362   // when consuming_buffers spans a co_yield, so 362   // when consuming_buffers spans a co_yield, so
363   // the GCC path uses a separate simple coroutine. 363   // the GCC path uses a separate simple coroutine.
364   consuming_buffers cb(buffers); 364   consuming_buffers cb(buffers);
365   while(total_written < total_size) 365   while(total_written < total_size)
366   { 366   {
367   auto inner = stream_.write_some(cb); 367   auto inner = stream_.write_some(cb);
368   if(!inner.await_ready()) 368   if(!inner.await_ready())
369   break; 369   break;
370   auto r = inner.await_resume(); 370   auto r = inner.await_resume();
371   if(r.ec) 371   if(r.ec)
372   co_return io_result<std::size_t>{ 372   co_return io_result<std::size_t>{
373   r.ec, total_written}; 373   r.ec, total_written};
374   cb.consume(std::get<0>(r.values)); 374   cb.consume(std::get<0>(r.values));
375   total_written += std::get<0>(r.values); 375   total_written += std::get<0>(r.values);
376   } 376   }
377   377  
378   if(total_written >= total_size) 378   if(total_written >= total_size)
379   co_return io_result<std::size_t>{ 379   co_return io_result<std::size_t>{
380   {}, total_written}; 380   {}, total_written};
381   381  
382   co_yield 0; 382   co_yield 0;
383   383  
384   while(total_written < total_size) 384   while(total_written < total_size)
385   { 385   {
386   auto r = 386   auto r =
387   co_await stream_.write_some(cb); 387   co_await stream_.write_some(cb);
388   cb.consume(std::get<0>(r.values)); 388   cb.consume(std::get<0>(r.values));
389   total_written += std::get<0>(r.values); 389   total_written += std::get<0>(r.values);
390   if(r.ec) 390   if(r.ec)
391   co_return io_result<std::size_t>{ 391   co_return io_result<std::size_t>{
392   r.ec, total_written}; 392   r.ec, total_written};
393   } 393   }
394   co_return io_result<std::size_t>{ 394   co_return io_result<std::size_t>{
395   {}, total_written}; 395   {}, total_written};
396   } 396   }
397   #endif 397   #endif
398   398  
399   #if defined(__GNUC__) && !defined(__clang__) 399   #if defined(__GNUC__) && !defined(__clang__)
400   #pragma GCC diagnostic pop 400   #pragma GCC diagnostic pop
401   #endif 401   #endif
402   }; 402   };
403   403  
404   template<WriteStream S> 404   template<WriteStream S>
405   write_now(S&) -> write_now<S>; 405   write_now(S&) -> write_now<S>;
406   406  
407   } // namespace capy 407   } // namespace capy
408   } // namespace boost 408   } // namespace boost
409   409  
410   #endif 410   #endif