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