include/boost/capy/io/any_stream.hpp

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