Line data 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 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 : if(this != &other)
143 : {
144 1 : if(storage_)
145 : {
146 0 : destroy_(stream_ptr_);
147 0 : ::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 : if(!committed && ptr) {
179 0 : static_cast<S*>(ptr)->~S();
180 0 : ::operator delete(self->storage_);
181 0 : self->storage_ = nullptr;
182 : }
183 1 : }
184 1 : } g{this};
185 :
186 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 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
|