TLA Line data 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 HIT 39 : ~any_stream()
98 : {
99 39 : if(storage_)
100 : {
101 3 : destroy_(stream_ptr_);
102 3 : ::operator delete(storage_);
103 : }
104 39 : }
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 1 : any_stream(any_stream&& other) noexcept
139 1 : : any_read_stream(std::move(static_cast<any_read_stream&>(other)))
140 1 : , any_write_stream(std::move(static_cast<any_write_stream&>(other)))
141 1 : , storage_(std::exchange(other.storage_, nullptr))
142 1 : , stream_ptr_(std::exchange(other.stream_ptr_, nullptr))
143 2 : , destroy_(std::exchange(other.destroy_, nullptr))
144 : {
145 1 : }
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 2 : operator=(any_stream&& other) noexcept
157 : {
158 2 : if(this != &other)
159 : {
160 2 : if(storage_)
161 : {
162 1 : destroy_(stream_ptr_);
163 1 : ::operator delete(storage_);
164 : }
165 : static_cast<any_read_stream&>(*this) =
166 2 : std::move(static_cast<any_read_stream&>(other));
167 : static_cast<any_write_stream&>(*this) =
168 2 : std::move(static_cast<any_write_stream&>(other));
169 2 : storage_ = std::exchange(other.storage_, nullptr);
170 2 : stream_ptr_ = std::exchange(other.stream_ptr_, nullptr);
171 2 : destroy_ = std::exchange(other.destroy_, nullptr);
172 : }
173 2 : 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 4 : any_stream(S s)
188 4 : {
189 : struct guard {
190 : any_stream* self;
191 : void* ptr = nullptr;
192 : bool committed = false;
193 4 : ~guard() {
194 4 : 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 4 : }
200 4 : } g{this};
201 :
202 4 : storage_ = ::operator new(sizeof(S));
203 4 : S* ptr = ::new(storage_) S(std::move(s));
204 4 : g.ptr = ptr;
205 4 : stream_ptr_ = ptr;
206 8 : destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); };
207 :
208 : // Initialize bases with pointer (reference semantics)
209 4 : static_cast<any_read_stream&>(*this) = any_read_stream(ptr);
210 4 : static_cast<any_write_stream&>(*this) = any_write_stream(ptr);
211 :
212 4 : g.committed = true;
213 4 : }
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 32 : any_stream(S* s)
226 : : any_read_stream(s)
227 32 : , any_write_stream(s)
228 : {
229 : // storage_ remains nullptr - no ownership
230 32 : }
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 12 : has_value() const noexcept
241 : {
242 19 : return any_read_stream::has_value() &&
243 19 : 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 2 : operator bool() const noexcept
255 : {
256 2 : return has_value();
257 : }
258 : };
259 :
260 : } // namespace capy
261 : } // namespace boost
262 :
263 : #endif
|