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 : @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 HIT 39 : ~any_stream()
76 : {
77 39 : any_read_stream::end_operation();
78 39 : any_write_stream::end_operation();
79 39 : if(storage_)
80 : {
81 7 : destroy_(stream_ptr_);
82 7 : ::operator delete(storage_);
83 : }
84 39 : }
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 1 : any_stream(any_stream&& other) noexcept
119 1 : : any_read_stream(std::move(static_cast<any_read_stream&>(other)))
120 1 : , any_write_stream(std::move(static_cast<any_write_stream&>(other)))
121 1 : , storage_(std::exchange(other.storage_, nullptr))
122 1 : , stream_ptr_(std::exchange(other.stream_ptr_, nullptr))
123 2 : , destroy_(std::exchange(other.destroy_, nullptr))
124 : {
125 1 : }
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 4 : operator=(any_stream&& other) noexcept
137 : {
138 4 : if(this != &other)
139 : {
140 4 : any_read_stream::end_operation();
141 4 : any_write_stream::end_operation();
142 4 : if(storage_)
143 : {
144 3 : destroy_(stream_ptr_);
145 3 : ::operator delete(storage_);
146 : }
147 : static_cast<any_read_stream&>(*this) =
148 4 : std::move(static_cast<any_read_stream&>(other));
149 : static_cast<any_write_stream&>(*this) =
150 4 : std::move(static_cast<any_write_stream&>(other));
151 4 : storage_ = std::exchange(other.storage_, nullptr);
152 4 : stream_ptr_ = std::exchange(other.stream_ptr_, nullptr);
153 4 : destroy_ = std::exchange(other.destroy_, nullptr);
154 : }
155 4 : 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 10 : any_stream(S s)
170 10 : {
171 : struct guard {
172 : any_stream* self;
173 : void* ptr = nullptr;
174 : bool committed = false;
175 10 : ~guard() {
176 10 : 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 10 : }
182 10 : } g{this};
183 :
184 10 : storage_ = ::operator new(sizeof(S));
185 10 : S* ptr = ::new(storage_) S(std::move(s));
186 10 : g.ptr = ptr;
187 10 : stream_ptr_ = ptr;
188 20 : destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); };
189 :
190 : // Initialize bases with pointer (reference semantics)
191 10 : static_cast<any_read_stream&>(*this) = any_read_stream(ptr);
192 10 : static_cast<any_write_stream&>(*this) = any_write_stream(ptr);
193 :
194 10 : g.committed = true;
195 10 : }
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 26 : any_stream(S* s)
208 : : any_read_stream(s)
209 26 : , any_write_stream(s)
210 : {
211 : // storage_ remains nullptr - no ownership
212 26 : }
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 12 : has_value() const noexcept
223 : {
224 19 : return any_read_stream::has_value() &&
225 19 : 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 2 : operator bool() const noexcept
237 : {
238 2 : return has_value();
239 : }
240 : };
241 :
242 : } // namespace capy
243 : } // namespace boost
244 :
245 : #endif
|