100.00% Lines (46/46) 100.00% Functions (9/9)
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 + // Copyright (c) 2026 Michael Vandeberg
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // 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) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
8   // 9   //
9   10  
10   #ifndef BOOST_CAPY_IO_ANY_STREAM_HPP 11   #ifndef BOOST_CAPY_IO_ANY_STREAM_HPP
11   #define BOOST_CAPY_IO_ANY_STREAM_HPP 12   #define BOOST_CAPY_IO_ANY_STREAM_HPP
12   13  
13   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/concept/read_stream.hpp> 15   #include <boost/capy/concept/read_stream.hpp>
15   #include <boost/capy/concept/write_stream.hpp> 16   #include <boost/capy/concept/write_stream.hpp>
16   #include <boost/capy/io/any_read_stream.hpp> 17   #include <boost/capy/io/any_read_stream.hpp>
17   #include <boost/capy/io/any_write_stream.hpp> 18   #include <boost/capy/io/any_write_stream.hpp>
18   19  
19   #include <concepts> 20   #include <concepts>
20   21  
21   namespace boost { 22   namespace boost {
22   namespace capy { 23   namespace capy {
23   24  
24 - /** Type-erased wrapper for bidirectional streams. 25 + /** Dispatches `read_some` and `write_some` through independent type-erased vtables.
25   26  
26   This class provides type erasure for any type satisfying both 27   This class provides type erasure for any type satisfying both
27   the @ref ReadStream and @ref WriteStream concepts, enabling 28   the @ref ReadStream and @ref WriteStream concepts, enabling
28   runtime polymorphism for bidirectional I/O operations. 29   runtime polymorphism for bidirectional I/O operations.
29   30  
30   Inherits from both @ref any_read_stream and @ref any_write_stream, 31   Inherits from both @ref any_read_stream and @ref any_write_stream,
31   providing `read_some` and `write_some` operations. Each base 32   providing `read_some` and `write_some` operations. Each base
32   maintains its own cached awaitable storage, allowing concurrent 33   maintains its own cached awaitable storage, allowing concurrent
33   read and write operations. 34   read and write operations.
34   35  
35   The wrapper supports two construction modes: 36   The wrapper supports two construction modes:
36   - **Owning**: Pass by value to transfer ownership. The wrapper 37   - **Owning**: Pass by value to transfer ownership. The wrapper
37   allocates storage and owns the stream. 38   allocates storage and owns the stream.
38   - **Reference**: Pass a pointer to wrap without ownership. The 39   - **Reference**: Pass a pointer to wrap without ownership. The
39   pointed-to stream must outlive this wrapper. 40   pointed-to stream must outlive this wrapper.
40   41  
41   @par Implicit Conversion 42   @par Implicit Conversion
42   This class implicitly converts to `any_read_stream&` or 43   This class implicitly converts to `any_read_stream&` or
43   `any_write_stream&`, allowing it to be passed to functions 44   `any_write_stream&`, allowing it to be passed to functions
44   that accept only one capability. However, do not move through 45   that accept only one capability. However, do not move through
45   a base reference as this would leave the other base in an 46   a base reference as this would leave the other base in an
46   invalid state. 47   invalid state.
47   48  
48   @par Thread Safety 49   @par Thread Safety
49   Not thread-safe. Concurrent operations of the same type 50   Not thread-safe. Concurrent operations of the same type
50   (two reads or two writes) are undefined behavior. One read 51   (two reads or two writes) are undefined behavior. One read
51   and one write may be in flight simultaneously. 52   and one write may be in flight simultaneously.
52   53  
53   @par Example 54   @par Example
54   @code 55   @code
  56 + void reader(any_read_stream&);
  57 + void writer(any_write_stream&);
  58 +
55   // Owning - takes ownership of the stream 59   // Owning - takes ownership of the stream
56 - any_stream stream(socket{ioc}); 60 + any_stream owning_stream(socket{ioc});
57   61  
58   // Reference - wraps without ownership 62   // Reference - wraps without ownership
59   socket sock(ioc); 63   socket sock(ioc);
60 - any_stream stream(&sock); 64 + any_stream ref_stream(&sock);
61   65  
62 - // Use read_some from any_read_stream base 66 + // Use read_some from the any_read_stream base
63 - mutable_buffer rbuf(rdata, rsize); 67 + char rdata[1024];
64 - auto [ec1, n1] = co_await stream.read_some(std::span(&rbuf, 1)); 68 + mutable_buffer rbuf(rdata, sizeof(rdata));
  69 + auto [ec1, n1] = co_await owning_stream.read_some(std::span(&rbuf, 1));
65   70  
66 - // Use write_some from any_write_stream base 71 + // Use write_some from the any_write_stream base
67 - const_buffer wbuf(wdata, wsize); 72 + char wdata[] = "hello";
68 - auto [ec2, n2] = co_await stream.write_some(std::span(&wbuf, 1)); 73 + const_buffer wbuf(wdata, sizeof(wdata));
  74 + auto [ec2, n2] = co_await owning_stream.write_some(std::span(&wbuf, 1));
69   75  
70   // Pass to functions expecting one capability 76   // Pass to functions expecting one capability
71 - void reader(any_read_stream&); 77 + reader(owning_stream); // Implicit upcast
72 - void writer(any_write_stream&); 78 + writer(owning_stream); // Implicit upcast
73 - reader(stream); // Implicit upcast  
74 - writer(stream); // Implicit upcast  
75   @endcode 79   @endcode
76   80  
77   @see any_read_stream, any_write_stream, ReadStream, WriteStream 81   @see any_read_stream, any_write_stream, ReadStream, WriteStream
78   */ 82   */
79   class any_stream 83   class any_stream
80   : public any_read_stream 84   : public any_read_stream
81   , public any_write_stream 85   , public any_write_stream
82   { 86   {
83   void* storage_ = nullptr; 87   void* storage_ = nullptr;
84   void* stream_ptr_ = nullptr; 88   void* stream_ptr_ = nullptr;
85   void (*destroy_)(void*) noexcept = nullptr; 89   void (*destroy_)(void*) noexcept = nullptr;
86   90  
87   public: 91   public:
88   /** Destructor. 92   /** Destructor.
89   93  
90   Destroys the owned stream (if any). Base class destructors 94   Destroys the owned stream (if any). Base class destructors
91   handle their cached awaitable storage. 95   handle their cached awaitable storage.
92   */ 96   */
HITCBC 93   39 ~any_stream() 97   39 ~any_stream()
94   { 98   {
HITCBC 95   39 if(storage_) 99   39 if(storage_)
96   { 100   {
HITCBC 97   3 destroy_(stream_ptr_); 101   3 destroy_(stream_ptr_);
HITCBC 98   3 ::operator delete(storage_); 102   3 ::operator delete(storage_);
99   } 103   }
HITCBC 100   39 } 104   39 }
101   105  
102   /** Construct a default instance. 106   /** Construct a default instance.
103   107  
104 - Constructs an empty wrapper. Operations on a default-constructed 108 + Constructs an empty wrapper. @ref has_value and `operator bool`
105 - wrapper result in undefined behavior. 109 + report the empty state; calling `read_some` or `write_some`
  110 + before the wrapper holds a stream is undefined behavior.
106   */ 111   */
107   any_stream() = default; 112   any_stream() = default;
108   113  
109   /** Non-copyable. 114   /** Non-copyable.
110   115  
111   The awaitable caches are per-instance and cannot be shared. 116   The awaitable caches are per-instance and cannot be shared.
  117 +
  118 + @param other The wrapper that would be copied.
112   */ 119   */
113 - any_stream(any_stream const&) = delete; 120 + any_stream(any_stream const& other) = delete;
114 - any_stream& operator=(any_stream const&) = delete; 121 +
  122 + /** Copy assignment is disabled.
  123 +
  124 + The awaitable caches are per-instance and cannot be shared.
  125 +
  126 + @param other The wrapper that would be assigned from.
  127 +
  128 + @return A reference to `*this`.
  129 + */
  130 + any_stream& operator=(any_stream const& other) = delete;
115   131  
116   /** Construct by moving. 132   /** Construct by moving.
117   133  
118   Transfers ownership from both bases and the owned stream (if any). 134   Transfers ownership from both bases and the owned stream (if any).
119   135  
120   @param other The wrapper to move from. 136   @param other The wrapper to move from.
121   */ 137   */
HITCBC 122   1 any_stream(any_stream&& other) noexcept 138   1 any_stream(any_stream&& other) noexcept
HITCBC 123   1 : any_read_stream(std::move(static_cast<any_read_stream&>(other))) 139   1 : any_read_stream(std::move(static_cast<any_read_stream&>(other)))
HITCBC 124   1 , any_write_stream(std::move(static_cast<any_write_stream&>(other))) 140   1 , any_write_stream(std::move(static_cast<any_write_stream&>(other)))
HITCBC 125   1 , storage_(std::exchange(other.storage_, nullptr)) 141   1 , storage_(std::exchange(other.storage_, nullptr))
HITCBC 126   1 , stream_ptr_(std::exchange(other.stream_ptr_, nullptr)) 142   1 , stream_ptr_(std::exchange(other.stream_ptr_, nullptr))
HITCBC 127   2 , destroy_(std::exchange(other.destroy_, nullptr)) 143   2 , destroy_(std::exchange(other.destroy_, nullptr))
128   { 144   {
HITCBC 129   1 } 145   1 }
130   146  
131   /** Assign by moving. 147   /** Assign by moving.
132   148  
133   Destroys any owned stream and releases existing resources, 149   Destroys any owned stream and releases existing resources,
134   then transfers ownership from `other`. 150   then transfers ownership from `other`.
135   151  
136   @param other The wrapper to move from. 152   @param other The wrapper to move from.
137   @return Reference to this wrapper. 153   @return Reference to this wrapper.
138   */ 154   */
139   any_stream& 155   any_stream&
HITCBC 140   2 operator=(any_stream&& other) noexcept 156   2 operator=(any_stream&& other) noexcept
141   { 157   {
HITCBC 142   2 if(this != &other) 158   2 if(this != &other)
143   { 159   {
HITCBC 144   2 if(storage_) 160   2 if(storage_)
145   { 161   {
HITCBC 146   1 destroy_(stream_ptr_); 162   1 destroy_(stream_ptr_);
HITCBC 147   1 ::operator delete(storage_); 163   1 ::operator delete(storage_);
148   } 164   }
149   static_cast<any_read_stream&>(*this) = 165   static_cast<any_read_stream&>(*this) =
HITCBC 150   2 std::move(static_cast<any_read_stream&>(other)); 166   2 std::move(static_cast<any_read_stream&>(other));
151   static_cast<any_write_stream&>(*this) = 167   static_cast<any_write_stream&>(*this) =
HITCBC 152   2 std::move(static_cast<any_write_stream&>(other)); 168   2 std::move(static_cast<any_write_stream&>(other));
HITCBC 153   2 storage_ = std::exchange(other.storage_, nullptr); 169   2 storage_ = std::exchange(other.storage_, nullptr);
HITCBC 154   2 stream_ptr_ = std::exchange(other.stream_ptr_, nullptr); 170   2 stream_ptr_ = std::exchange(other.stream_ptr_, nullptr);
HITCBC 155   2 destroy_ = std::exchange(other.destroy_, nullptr); 171   2 destroy_ = std::exchange(other.destroy_, nullptr);
156   } 172   }
HITCBC 157   2 return *this; 173   2 return *this;
158   } 174   }
159   175  
160   /** Construct by taking ownership of a bidirectional stream. 176   /** Construct by taking ownership of a bidirectional stream.
161   177  
162   Allocates storage and moves the stream into this wrapper. 178   Allocates storage and moves the stream into this wrapper.
163 - The wrapper owns the stream and will destroy it. 179 + The wrapper owns the stream and destroys it.
164   180  
165   @param s The stream to take ownership of. Must satisfy both 181   @param s The stream to take ownership of. Must satisfy both
166   ReadStream and WriteStream concepts. 182   ReadStream and WriteStream concepts.
167   */ 183   */
168   template<class S> 184   template<class S>
169   requires ReadStream<S> && WriteStream<S> && 185   requires ReadStream<S> && WriteStream<S> &&
170   (!std::same_as<std::decay_t<S>, any_stream>) 186   (!std::same_as<std::decay_t<S>, any_stream>)
HITCBC 171   4 any_stream(S s) 187   4 any_stream(S s)
HITCBC 172   4 { 188   4 {
173   struct guard { 189   struct guard {
174   any_stream* self; 190   any_stream* self;
175   void* ptr = nullptr; 191   void* ptr = nullptr;
176   bool committed = false; 192   bool committed = false;
HITCBC 177   4 ~guard() { 193   4 ~guard() {
HITCBC 178   4 if(!committed && ptr) { 194   4 if(!committed && ptr) {
179   static_cast<S*>(ptr)->~S(); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 195   static_cast<S*>(ptr)->~S(); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
180   ::operator delete(self->storage_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 196   ::operator delete(self->storage_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
181   self->storage_ = nullptr; // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 197   self->storage_ = nullptr; // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
182   } 198   }
HITCBC 183   4 } 199   4 }
HITCBC 184   4 } g{this}; 200   4 } g{this};
185   201  
HITCBC 186   4 storage_ = ::operator new(sizeof(S)); 202   4 storage_ = ::operator new(sizeof(S));
HITCBC 187   4 S* ptr = ::new(storage_) S(std::move(s)); 203   4 S* ptr = ::new(storage_) S(std::move(s));
HITCBC 188   4 g.ptr = ptr; 204   4 g.ptr = ptr;
HITCBC 189   4 stream_ptr_ = ptr; 205   4 stream_ptr_ = ptr;
HITCBC 190   8 destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); }; 206   8 destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); };
191   207  
192   // Initialize bases with pointer (reference semantics) 208   // Initialize bases with pointer (reference semantics)
HITCBC 193   4 static_cast<any_read_stream&>(*this) = any_read_stream(ptr); 209   4 static_cast<any_read_stream&>(*this) = any_read_stream(ptr);
HITCBC 194   4 static_cast<any_write_stream&>(*this) = any_write_stream(ptr); 210   4 static_cast<any_write_stream&>(*this) = any_write_stream(ptr);
195   211  
HITCBC 196   4 g.committed = true; 212   4 g.committed = true;
HITCBC 197   4 } 213   4 }
198   214  
199   /** Construct by wrapping a bidirectional stream without ownership. 215   /** Construct by wrapping a bidirectional stream without ownership.
200   216  
201   Wraps the given stream by pointer. The stream must remain 217   Wraps the given stream by pointer. The stream must remain
202   valid for the lifetime of this wrapper. 218   valid for the lifetime of this wrapper.
203   219  
204   @param s Pointer to the stream to wrap. Must satisfy both 220   @param s Pointer to the stream to wrap. Must satisfy both
205   ReadStream and WriteStream concepts. 221   ReadStream and WriteStream concepts.
206   */ 222   */
207   template<class S> 223   template<class S>
208   requires ReadStream<S> && WriteStream<S> 224   requires ReadStream<S> && WriteStream<S>
HITCBC 209   32 any_stream(S* s) 225   32 any_stream(S* s)
210   : any_read_stream(s) 226   : any_read_stream(s)
HITCBC 211   32 , any_write_stream(s) 227   32 , any_write_stream(s)
212   { 228   {
213   // storage_ remains nullptr - no ownership 229   // storage_ remains nullptr - no ownership
HITCBC 214   32 } 230   32 }
215   231  
216   /** Check if the wrapper contains a valid stream. 232   /** Check if the wrapper contains a valid stream.
217   233  
218   Both bases must be valid for the wrapper to be valid. 234   Both bases must be valid for the wrapper to be valid.
219   235  
220   @return `true` if wrapping a stream, `false` if default-constructed 236   @return `true` if wrapping a stream, `false` if default-constructed
221   or moved-from. 237   or moved-from.
222   */ 238   */
223   bool 239   bool
HITCBC 224   12 has_value() const noexcept 240   12 has_value() const noexcept
225   { 241   {
HITCBC 226   19 return any_read_stream::has_value() && 242   19 return any_read_stream::has_value() &&
HITCBC 227   19 any_write_stream::has_value(); 243   19 any_write_stream::has_value();
228   } 244   }
229   245  
230   /** Check if the wrapper contains a valid stream. 246   /** Check if the wrapper contains a valid stream.
231   247  
232   Both bases must be valid for the wrapper to be valid. 248   Both bases must be valid for the wrapper to be valid.
233   249  
234   @return `true` if wrapping a stream, `false` if default-constructed 250   @return `true` if wrapping a stream, `false` if default-constructed
235   or moved-from. 251   or moved-from.
236   */ 252   */
237   explicit 253   explicit
HITCBC 238   2 operator bool() const noexcept 254   2 operator bool() const noexcept
239   { 255   {
HITCBC 240   2 return has_value(); 256   2 return has_value();
241   } 257   }
242   }; 258   };
243   259  
244   } // namespace capy 260   } // namespace capy
245   } // namespace boost 261   } // namespace boost
246   262  
247   #endif 263   #endif