libs/capy/include/boost/capy/io/any_stream.hpp

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