100.00% Lines (70/70) 100.00% Functions (22/22)
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/buffer_slice.hpp> 16   #include <boost/capy/buffers/buffer_slice.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   71 op_type get_return_object() 101   71 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   71 promise_type>::from_promise(*this)}; 105   71 promise_type>::from_promise(*this)};
106   } 106   }
107   107  
HITCBC 108   71 auto initial_suspend() noexcept 108   71 auto initial_suspend() noexcept
109   { 109   {
110   #if BOOST_CAPY_WRITE_NOW_WORKAROUND 110   #if BOOST_CAPY_WRITE_NOW_WORKAROUND
HITCBC 111   71 return std::suspend_always{}; 111   71 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   69 auto final_suspend() noexcept 117   69 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   69 bool await_ready() const noexcept 123   69 bool await_ready() const noexcept
124   { 124   {
HITCBC 125   69 return false; 125   69 return false;
126   } 126   }
127   127  
HITCBC 128   69 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 128   69 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
129   { 129   {
HITCBC 130   69 p_->done_ = true; 130   69 p_->done_ = true;
HITCBC 131   69 if(!p_->cont_) 131   69 if(!p_->cont_)
132   return std::noop_coroutine(); // LCOV_EXCL_LINE cont_ always set on this (suspend_always) path 132   return std::noop_coroutine(); // LCOV_EXCL_LINE cont_ always set on this (suspend_always) path
HITCBC 133   69 return p_->cont_; 133   69 return p_->cont_;
134   } 134   }
135   135  
136   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 136   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
137   }; 137   };
HITCBC 138   69 return awaiter{this}; 138   69 return awaiter{this};
139   } 139   }
140   140  
HITCBC 141   47 void return_value( 141   47 void return_value(
142   io_result<std::size_t> r) noexcept 142   io_result<std::size_t> r) noexcept
143   { 143   {
HITCBC 144   47 result_ = r; 144   47 result_ = r;
HITCBC 145   47 } 145   47 }
146   146  
HITCBC 147   22 void unhandled_exception() 147   22 void unhandled_exception()
148   { 148   {
HITCBC 149   22 ep_ = std::current_exception(); 149   22 ep_ = std::current_exception();
HITCBC 150   22 } 150   22 }
151   151  
152   std::suspend_always yield_value(int) noexcept 152   std::suspend_always yield_value(int) noexcept
153   { 153   {
154   return {}; 154   return {};
155   } 155   }
156   156  
157   template<class A> 157   template<class A>
HITCBC 158   85 auto await_transform(A&& a) 158   85 auto await_transform(A&& a)
159   { 159   {
160   using decayed = std::decay_t<A>; 160   using decayed = std::decay_t<A>;
161   if constexpr (IoAwaitable<decayed>) 161   if constexpr (IoAwaitable<decayed>)
162   { 162   {
163   struct wrapper 163   struct wrapper
164   { 164   {
165   decayed inner_; 165   decayed inner_;
166   promise_type* p_; 166   promise_type* p_;
167   167  
HITCBC 168   85 bool await_ready() 168   85 bool await_ready()
169   { 169   {
HITCBC 170   85 return inner_.await_ready(); 170   85 return inner_.await_ready();
171   } 171   }
172   172  
HITCBC 173   1 std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) 173   1 std::coroutine_handle<> await_suspend(std::coroutine_handle<> h)
174   { 174   {
HITCBC 175   1 return detail::call_await_suspend( 175   1 return detail::call_await_suspend(
176   &inner_, h, 176   &inner_, h,
HITCBC 177   1 p_->env_); 177   1 p_->env_);
178   } 178   }
179   179  
HITCBC 180   85 decltype(auto) await_resume() 180   85 decltype(auto) await_resume()
181   { 181   {
HITCBC 182   85 return inner_.await_resume(); 182   85 return inner_.await_resume();
183   } 183   }
184   }; 184   };
185   return wrapper{ 185   return wrapper{
HITCBC 186   85 std::forward<A>(a), this}; 186   85 std::forward<A>(a), this};
187   } 187   }
188   else 188   else
189   { 189   {
190   return std::forward<A>(a); 190   return std::forward<A>(a);
191   } 191   }
192   } 192   }
193   193  
194   static void* 194   static void*
HITCBC 195   71 operator new( 195   71 operator new(
196   std::size_t size, 196   std::size_t size,
197   write_now& self, 197   write_now& self,
198   auto&) 198   auto&)
199   { 199   {
HITCBC 200   71 if(self.cached_frame_ && 200   71 if(self.cached_frame_ &&
HITCBC 201   5 self.cached_size_ >= size) 201   5 self.cached_size_ >= size)
HITCBC 202   4 return self.cached_frame_; 202   4 return self.cached_frame_;
HITCBC 203   67 void* p = ::operator new(size); 203   67 void* p = ::operator new(size);
HITCBC 204   67 if(self.cached_frame_) 204   67 if(self.cached_frame_)
HITCBC 205   1 ::operator delete(self.cached_frame_); 205   1 ::operator delete(self.cached_frame_);
HITCBC 206   67 self.cached_frame_ = p; 206   67 self.cached_frame_ = p;
HITCBC 207   67 self.cached_size_ = size; 207   67 self.cached_size_ = size;
HITCBC 208   67 return p; 208   67 return p;
209   } 209   }
210   210  
211   static void 211   static void
HITCBC 212   71 operator delete(void*, std::size_t) noexcept 212   71 operator delete(void*, std::size_t) noexcept
213   { 213   {
HITCBC 214   71 } 214   71 }
215   }; 215   };
216   216  
217   std::coroutine_handle<promise_type> h_; 217   std::coroutine_handle<promise_type> h_;
218   218  
HITCBC 219   140 ~op_type() 219   140 ~op_type()
220   { 220   {
HITCBC 221   140 if(h_) 221   140 if(h_)
HITCBC 222   71 h_.destroy(); 222   71 h_.destroy();
HITCBC 223   140 } 223   140 }
224   224  
225   op_type(op_type const&) = delete; 225   op_type(op_type const&) = delete;
226   op_type& operator=(op_type const&) = delete; 226   op_type& operator=(op_type const&) = delete;
227   227  
HITCBC 228   69 op_type(op_type&& other) noexcept 228   69 op_type(op_type&& other) noexcept
HITCBC 229   69 : h_(std::exchange(other.h_, nullptr)) 229   69 : h_(std::exchange(other.h_, nullptr))
230   { 230   {
HITCBC 231   69 } 231   69 }
232   232  
233   op_type& operator=(op_type&&) = delete; 233   op_type& operator=(op_type&&) = delete;
234   234  
HITCBC 235   69 bool await_ready() const noexcept 235   69 bool await_ready() const noexcept
236   { 236   {
HITCBC 237   69 return h_.promise().done_; 237   69 return h_.promise().done_;
238   } 238   }
239   239  
HITCBC 240   69 std::coroutine_handle<> await_suspend( 240   69 std::coroutine_handle<> await_suspend(
241   std::coroutine_handle<> cont, 241   std::coroutine_handle<> cont,
242   io_env const* env) 242   io_env const* env)
243   { 243   {
HITCBC 244   69 auto& p = h_.promise(); 244   69 auto& p = h_.promise();
HITCBC 245   69 p.cont_ = cont; 245   69 p.cont_ = cont;
HITCBC 246   69 p.env_ = env; 246   69 p.env_ = env;
HITCBC 247   69 return h_; 247   69 return h_;
248   } 248   }
249   249  
HITCBC 250   69 io_result<std::size_t> await_resume() 250   69 io_result<std::size_t> await_resume()
251   { 251   {
HITCBC 252   69 auto& p = h_.promise(); 252   69 auto& p = h_.promise();
HITCBC 253   69 if(p.ep_) 253   69 if(p.ep_)
HITCBC 254   22 std::rethrow_exception(p.ep_); 254   22 std::rethrow_exception(p.ep_);
HITCBC 255   47 return p.result_; 255   47 return p.result_;
256   } 256   }
257   257  
258   private: 258   private:
HITCBC 259   71 explicit op_type( 259   71 explicit op_type(
260   std::coroutine_handle<promise_type> h) 260   std::coroutine_handle<promise_type> h)
HITCBC 261   71 : h_(h) 261   71 : h_(h)
262   { 262   {
HITCBC 263   71 } 263   71 }
264   }; 264   };
265   265  
266   public: 266   public:
267   /** Destructor. Frees the cached coroutine frame. */ 267   /** Destructor. Frees the cached coroutine frame. */
HITCBC 268   66 ~write_now() 268   66 ~write_now()
269   { 269   {
HITCBC 270   66 if(cached_frame_) 270   66 if(cached_frame_)
HITCBC 271   66 ::operator delete(cached_frame_); 271   66 ::operator delete(cached_frame_);
HITCBC 272   66 } 272   66 }
273   273  
274   /** Construct from a stream reference. 274   /** Construct from a stream reference.
275   275  
276   @param s The stream to write to. Must outlive this object. 276   @param s The stream to write to. Must outlive this object.
277   */ 277   */
278   explicit 278   explicit
HITCBC 279   66 write_now(Stream& s) noexcept 279   66 write_now(Stream& s) noexcept
HITCBC 280   66 : stream_(s) 280   66 : stream_(s)
281   { 281   {
HITCBC 282   66 } 282   66 }
283   283  
284   write_now(write_now const&) = delete; 284   write_now(write_now const&) = delete;
285   write_now& operator=(write_now const&) = delete; 285   write_now& operator=(write_now const&) = delete;
286   286  
287   /** Eagerly write the entire buffer sequence. 287   /** Eagerly write the entire buffer sequence.
288   288  
289   Writes data to the stream by calling `write_some` repeatedly 289   Writes data to the stream by calling `write_some` repeatedly
290   until the entire buffer sequence is written or an error 290   until the entire buffer sequence is written or an error
291   occurs. The operation attempts to complete synchronously: 291   occurs. The operation attempts to complete synchronously:
292   if every `write_some` completes without suspending, the 292   if every `write_some` completes without suspending, the
293   entire operation finishes in `await_ready`. 293   entire operation finishes in `await_ready`.
294   294  
295   When the fast path cannot complete, the coroutine suspends 295   When the fast path cannot complete, the coroutine suspends
296   and continues asynchronously. The internal coroutine frame 296   and continues asynchronously. The internal coroutine frame
297   is cached and reused across calls. 297   is cached and reused across calls.
298   298  
299   @param buffers The buffer sequence to write. Passed by 299   @param buffers The buffer sequence to write. Passed by
300   value to ensure the sequence lives in the coroutine 300   value to ensure the sequence lives in the coroutine
301   frame across suspension points. 301   frame across suspension points.
302   302  
303   @return An awaitable that await-returns `(error_code,std::size_t)`. 303   @return An awaitable that await-returns `(error_code,std::size_t)`.
304   On success, `n` equals `buffer_size(buffers)`. On 304   On success, `n` equals `buffer_size(buffers)`. On
305   error, `n` is the number of bytes written before the 305   error, `n` is the number of bytes written before the
306   error. Compare error codes to conditions: 306   error. Compare error codes to conditions:
307   @li `cond::canceled` - Operation was cancelled 307   @li `cond::canceled` - Operation was cancelled
308   @li `std::errc::broken_pipe` - Peer closed connection 308   @li `std::errc::broken_pipe` - Peer closed connection
309   309  
310   @par Example 310   @par Example
311   311  
312   @code 312   @code
313   write_now wn( stream ); 313   write_now wn( stream );
314   auto [ec, n] = co_await wn( make_buffer( body ) ); 314   auto [ec, n] = co_await wn( make_buffer( body ) );
315   if( ec ) 315   if( ec )
316   detail::throw_system_error( ec ); 316   detail::throw_system_error( ec );
317   @endcode 317   @endcode
318   318  
319   @see write, write_some, WriteStream 319   @see write, write_some, WriteStream
320   */ 320   */
321   // GCC falsely warns that the coroutine promise's 321   // GCC falsely warns that the coroutine promise's
322   // placement operator new(size_t, write_now&, auto&) 322   // placement operator new(size_t, write_now&, auto&)
323   // mismatches operator delete(void*, size_t). Per the 323   // mismatches operator delete(void*, size_t). Per the
324   // standard, coroutine deallocation lookup is separate. 324   // standard, coroutine deallocation lookup is separate.
325   #if defined(__GNUC__) && !defined(__clang__) 325   #if defined(__GNUC__) && !defined(__clang__)
326   #pragma GCC diagnostic push 326   #pragma GCC diagnostic push
327   #pragma GCC diagnostic ignored "-Wmismatched-new-delete" 327   #pragma GCC diagnostic ignored "-Wmismatched-new-delete"
328   #endif 328   #endif
329   329  
330   #if BOOST_CAPY_WRITE_NOW_WORKAROUND 330   #if BOOST_CAPY_WRITE_NOW_WORKAROUND
331   template<ConstBufferSequence Buffers> 331   template<ConstBufferSequence Buffers>
332   op_type 332   op_type
HITCBC 333   71 operator()(Buffers buffers) 333   71 operator()(Buffers buffers)
334   { 334   {
335   std::size_t const total_size = buffer_size(buffers); 335   std::size_t const total_size = buffer_size(buffers);
336   std::size_t total_written = 0; 336   std::size_t total_written = 0;
337   auto cb = buffer_slice(buffers); 337   auto cb = buffer_slice(buffers);
338   while(total_written < total_size) 338   while(total_written < total_size)
339   { 339   {
340   auto r = 340   auto r =
341   co_await stream_.write_some(cb.data()); 341   co_await stream_.write_some(cb.data());
342   cb.remove_prefix(std::get<0>(r.values)); 342   cb.remove_prefix(std::get<0>(r.values));
343   total_written += std::get<0>(r.values); 343   total_written += std::get<0>(r.values);
344   if(r.ec) 344   if(r.ec)
345   co_return io_result<std::size_t>{ 345   co_return io_result<std::size_t>{
346   r.ec, total_written}; 346   r.ec, total_written};
347   } 347   }
348   co_return io_result<std::size_t>{ 348   co_return io_result<std::size_t>{
349   {}, total_written}; 349   {}, total_written};
HITCBC 350   142 } 350   142 }
351   #else 351   #else
352   template<ConstBufferSequence Buffers> 352   template<ConstBufferSequence Buffers>
353   op_type 353   op_type
354   operator()(Buffers buffers) 354   operator()(Buffers buffers)
355   { 355   {
356   std::size_t const total_size = buffer_size(buffers); 356   std::size_t const total_size = buffer_size(buffers);
357   std::size_t total_written = 0; 357   std::size_t total_written = 0;
358   358  
359   // GCC ICE in expand_expr_real_1 (expr.cc:11376) 359   // GCC ICE in expand_expr_real_1 (expr.cc:11376)
360   // when the buffer slice spans a co_yield, so 360   // when the buffer slice spans a co_yield, so
361   // the GCC path uses a separate simple coroutine. 361   // the GCC path uses a separate simple coroutine.
362   auto cb = buffer_slice(buffers); 362   auto cb = buffer_slice(buffers);
363   while(total_written < total_size) 363   while(total_written < total_size)
364   { 364   {
365   auto inner = stream_.write_some(cb.data()); 365   auto inner = stream_.write_some(cb.data());
366   if(!inner.await_ready()) 366   if(!inner.await_ready())
367   break; 367   break;
368   auto r = inner.await_resume(); 368   auto r = inner.await_resume();
369   if(r.ec) 369   if(r.ec)
370   co_return io_result<std::size_t>{ 370   co_return io_result<std::size_t>{
371   r.ec, total_written}; 371   r.ec, total_written};
372   cb.remove_prefix(std::get<0>(r.values)); 372   cb.remove_prefix(std::get<0>(r.values));
373   total_written += std::get<0>(r.values); 373   total_written += std::get<0>(r.values);
374   } 374   }
375   375  
376   if(total_written >= total_size) 376   if(total_written >= total_size)
377   co_return io_result<std::size_t>{ 377   co_return io_result<std::size_t>{
378   {}, total_written}; 378   {}, total_written};
379   379  
380   co_yield 0; 380   co_yield 0;
381   381  
382   while(total_written < total_size) 382   while(total_written < total_size)
383   { 383   {
384   auto r = 384   auto r =
385   co_await stream_.write_some(cb.data()); 385   co_await stream_.write_some(cb.data());
386   cb.remove_prefix(std::get<0>(r.values)); 386   cb.remove_prefix(std::get<0>(r.values));
387   total_written += std::get<0>(r.values); 387   total_written += std::get<0>(r.values);
388   if(r.ec) 388   if(r.ec)
389   co_return io_result<std::size_t>{ 389   co_return io_result<std::size_t>{
390   r.ec, total_written}; 390   r.ec, total_written};
391   } 391   }
392   co_return io_result<std::size_t>{ 392   co_return io_result<std::size_t>{
393   {}, total_written}; 393   {}, total_written};
394   } 394   }
395   #endif 395   #endif
396   396  
397   #if defined(__GNUC__) && !defined(__clang__) 397   #if defined(__GNUC__) && !defined(__clang__)
398   #pragma GCC diagnostic pop 398   #pragma GCC diagnostic pop
399   #endif 399   #endif
400   }; 400   };
401   401  
402   template<WriteStream S> 402   template<WriteStream S>
403   write_now(S&) -> write_now<S>; 403   write_now(S&) -> write_now<S>;
404   404  
405   } // namespace capy 405   } // namespace capy
406   } // namespace boost 406   } // namespace boost
407   407  
408   #endif 408   #endif