include/boost/capy/io/any_stream.hpp

100.0% Lines (46/3/49) 100.0% List of functions (14/0/14)
any_stream.hpp
f(x) Functions (14)
Function Calls Lines Blocks
boost::capy::any_stream::~any_stream() :97 39x 100.0% 100.0% boost::capy::any_stream::any_stream(boost::capy::any_stream&&) :138 1x 100.0% 100.0% boost::capy::any_stream::operator=(boost::capy::any_stream&&) :156 2x 100.0% 100.0% boost::capy::any_stream::any_stream<boost::capy::(anonymous namespace)::mock_stream>(boost::capy::(anonymous namespace)::mock_stream) :187 3x 100.0% 67.0% boost::capy::any_stream::any_stream<boost::capy::test::stream>(boost::capy::test::stream) :187 1x 100.0% 67.0% boost::capy::any_stream::any_stream<boost::capy::(anonymous namespace)::mock_stream>(boost::capy::(anonymous namespace)::mock_stream)::guard::~guard() :193 3x 100.0% 33.0% boost::capy::any_stream::any_stream<boost::capy::test::stream>(boost::capy::test::stream)::guard::~guard() :193 1x 100.0% 33.0% boost::capy::any_stream::any_stream<boost::capy::(anonymous namespace)::mock_stream>(boost::capy::(anonymous namespace)::mock_stream)::{lambda(void*)#1}::operator()(void*) const :206 3x 100.0% 100.0% boost::capy::any_stream::any_stream<boost::capy::test::stream>(boost::capy::test::stream)::{lambda(void*)#1}::operator()(void*) const :206 1x 100.0% 100.0% boost::capy::any_stream::any_stream<(anonymous namespace)::tcp::socket>((anonymous namespace)::tcp::socket*) :225 3x 100.0% 80.0% boost::capy::any_stream::any_stream<boost::capy::(anonymous namespace)::mock_stream>(boost::capy::(anonymous namespace)::mock_stream*) :225 22x 100.0% 80.0% boost::capy::any_stream::any_stream<boost::capy::test::stream>(boost::capy::test::stream*) :225 7x 100.0% 80.0% boost::capy::any_stream::has_value() const :240 12x 100.0% 100.0% boost::capy::any_stream::operator bool() const :254 2x 100.0% 100.0%
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 @code
56 void reader(any_read_stream&);
57 void writer(any_write_stream&);
58
59 // Owning - takes ownership of the stream
60 any_stream owning_stream(socket{ioc});
61
62 // Reference - wraps without ownership
63 socket sock(ioc);
64 any_stream ref_stream(&sock);
65
66 // Use read_some from the any_read_stream base
67 char rdata[1024];
68 mutable_buffer rbuf(rdata, sizeof(rdata));
69 auto [ec1, n1] = co_await owning_stream.read_some(std::span(&rbuf, 1));
70
71 // Use write_some from the any_write_stream base
72 char wdata[] = "hello";
73 const_buffer wbuf(wdata, sizeof(wdata));
74 auto [ec2, n2] = co_await owning_stream.write_some(std::span(&wbuf, 1));
75
76 // Pass to functions expecting one capability
77 reader(owning_stream); // Implicit upcast
78 writer(owning_stream); // Implicit upcast
79 @endcode
80
81 @see any_read_stream, any_write_stream, ReadStream, WriteStream
82 */
83 class any_stream
84 : public any_read_stream
85 , public any_write_stream
86 {
87 void* storage_ = nullptr;
88 void* stream_ptr_ = nullptr;
89 void (*destroy_)(void*) noexcept = nullptr;
90
91 public:
92 /** Destructor.
93
94 Destroys the owned stream (if any). Base class destructors
95 handle their cached awaitable storage.
96 */
97 39x ~any_stream()
98 {
99 39x if(storage_)
100 {
101 3x destroy_(stream_ptr_);
102 3x ::operator delete(storage_);
103 }
104 39x }
105
106 /** Construct a default instance.
107
108 Constructs an empty wrapper. @ref has_value and `operator bool`
109 report the empty state; calling `read_some` or `write_some`
110 before the wrapper holds a stream is undefined behavior.
111 */
112 any_stream() = default;
113
114 /** Non-copyable.
115
116 The awaitable caches are per-instance and cannot be shared.
117
118 @param other The wrapper that would be copied.
119 */
120 any_stream(any_stream const& other) = 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;
131
132 /** Construct by moving.
133
134 Transfers ownership from both bases and the owned stream (if any).
135
136 @param other The wrapper to move from.
137 */
138 1x any_stream(any_stream&& other) noexcept
139 1x : any_read_stream(std::move(static_cast<any_read_stream&>(other)))
140 1x , any_write_stream(std::move(static_cast<any_write_stream&>(other)))
141 1x , storage_(std::exchange(other.storage_, nullptr))
142 1x , stream_ptr_(std::exchange(other.stream_ptr_, nullptr))
143 2x , destroy_(std::exchange(other.destroy_, nullptr))
144 {
145 1x }
146
147 /** Assign by moving.
148
149 Destroys any owned stream and releases existing resources,
150 then transfers ownership from `other`.
151
152 @param other The wrapper to move from.
153 @return Reference to this wrapper.
154 */
155 any_stream&
156 2x operator=(any_stream&& other) noexcept
157 {
158 2x if(this != &other)
159 {
160 2x if(storage_)
161 {
162 1x destroy_(stream_ptr_);
163 1x ::operator delete(storage_);
164 }
165 static_cast<any_read_stream&>(*this) =
166 2x std::move(static_cast<any_read_stream&>(other));
167 static_cast<any_write_stream&>(*this) =
168 2x std::move(static_cast<any_write_stream&>(other));
169 2x storage_ = std::exchange(other.storage_, nullptr);
170 2x stream_ptr_ = std::exchange(other.stream_ptr_, nullptr);
171 2x destroy_ = std::exchange(other.destroy_, nullptr);
172 }
173 2x return *this;
174 }
175
176 /** Construct by taking ownership of a bidirectional stream.
177
178 Allocates storage and moves the stream into this wrapper.
179 The wrapper owns the stream and destroys it.
180
181 @param s The stream to take ownership of. Must satisfy both
182 ReadStream and WriteStream concepts.
183 */
184 template<class S>
185 requires ReadStream<S> && WriteStream<S> &&
186 (!std::same_as<std::decay_t<S>, any_stream>)
187 4x any_stream(S s)
188 4x {
189 struct guard {
190 any_stream* self;
191 void* ptr = nullptr;
192 bool committed = false;
193 4x ~guard() {
194 4x if(!committed && ptr) {
195 static_cast<S*>(ptr)->~S(); // 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
197 self->storage_ = nullptr; // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
198 }
199 4x }
200 4x } g{this};
201
202 4x storage_ = ::operator new(sizeof(S));
203 4x S* ptr = ::new(storage_) S(std::move(s));
204 4x g.ptr = ptr;
205 4x stream_ptr_ = ptr;
206 8x destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); };
207
208 // Initialize bases with pointer (reference semantics)
209 4x static_cast<any_read_stream&>(*this) = any_read_stream(ptr);
210 4x static_cast<any_write_stream&>(*this) = any_write_stream(ptr);
211
212 4x g.committed = true;
213 4x }
214
215 /** Construct by wrapping a bidirectional stream without ownership.
216
217 Wraps the given stream by pointer. The stream must remain
218 valid for the lifetime of this wrapper.
219
220 @param s Pointer to the stream to wrap. Must satisfy both
221 ReadStream and WriteStream concepts.
222 */
223 template<class S>
224 requires ReadStream<S> && WriteStream<S>
225 32x any_stream(S* s)
226 : any_read_stream(s)
227 32x , any_write_stream(s)
228 {
229 // storage_ remains nullptr - no ownership
230 32x }
231
232 /** Check if the wrapper contains a valid stream.
233
234 Both bases must be valid for the wrapper to be valid.
235
236 @return `true` if wrapping a stream, `false` if default-constructed
237 or moved-from.
238 */
239 bool
240 12x has_value() const noexcept
241 {
242 19x return any_read_stream::has_value() &&
243 19x any_write_stream::has_value();
244 }
245
246 /** Check if the wrapper contains a valid stream.
247
248 Both bases must be valid for the wrapper to be valid.
249
250 @return `true` if wrapping a stream, `false` if default-constructed
251 or moved-from.
252 */
253 explicit
254 2x operator bool() const noexcept
255 {
256 2x return has_value();
257 }
258 };
259
260 } // namespace capy
261 } // namespace boost
262
263 #endif
264