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 : Destroys the owned stream (if any). Base class destructors
72 : handle their cached awaitable storage.
73 : */
74 HIT 39 : ~any_stream()
75 : {
76 39 : if(storage_)
77 : {
78 3 : destroy_(stream_ptr_);
79 3 : ::operator delete(storage_);
80 : }
81 39 : }
82 :
83 : /** Construct a default instance.
84 :
85 : Constructs an empty wrapper. @ref has_value and `operator bool`
86 : report the empty state; calling `read_some` or `write_some`
87 : before the wrapper holds a stream is undefined behavior.
88 : */
89 : any_stream() = default;
90 :
91 : /** Non-copyable.
92 :
93 : The awaitable caches are per-instance and cannot be shared.
94 :
95 : @param other The wrapper that would be copied.
96 : */
97 : any_stream(any_stream const& other) = delete;
98 :
99 : /** Copy assignment is disabled.
100 :
101 : The awaitable caches are per-instance and cannot be shared.
102 :
103 : @param other The wrapper that would be assigned from.
104 :
105 : @return A reference to `*this`.
106 : */
107 : any_stream& operator=(any_stream const& other) = delete;
108 :
109 : /** Construct by moving.
110 :
111 : Transfers ownership from both bases and the owned stream (if any).
112 :
113 : @param other The wrapper to move from.
114 : */
115 1 : any_stream(any_stream&& other) noexcept
116 1 : : any_read_stream(std::move(static_cast<any_read_stream&>(other)))
117 1 : , any_write_stream(std::move(static_cast<any_write_stream&>(other)))
118 1 : , storage_(std::exchange(other.storage_, nullptr))
119 1 : , stream_ptr_(std::exchange(other.stream_ptr_, nullptr))
120 2 : , destroy_(std::exchange(other.destroy_, nullptr))
121 : {
122 1 : }
123 :
124 : /** Assign by moving.
125 :
126 : Destroys any owned stream and releases existing resources,
127 : then transfers ownership from `other`.
128 :
129 : @param other The wrapper to move from.
130 : @return Reference to this wrapper.
131 : */
132 : any_stream&
133 2 : operator=(any_stream&& other) noexcept
134 : {
135 2 : if(this != &other)
136 : {
137 2 : if(storage_)
138 : {
139 1 : destroy_(stream_ptr_);
140 1 : ::operator delete(storage_);
141 : }
142 : static_cast<any_read_stream&>(*this) =
143 2 : std::move(static_cast<any_read_stream&>(other));
144 : static_cast<any_write_stream&>(*this) =
145 2 : std::move(static_cast<any_write_stream&>(other));
146 2 : storage_ = std::exchange(other.storage_, nullptr);
147 2 : stream_ptr_ = std::exchange(other.stream_ptr_, nullptr);
148 2 : destroy_ = std::exchange(other.destroy_, nullptr);
149 : }
150 2 : return *this;
151 : }
152 :
153 : /** Construct by taking ownership of a bidirectional stream.
154 :
155 : Allocates storage and moves the stream into this wrapper.
156 : The wrapper owns the stream and destroys it.
157 :
158 : @param s The stream to take ownership of. Must satisfy both
159 : ReadStream and WriteStream concepts.
160 : */
161 : template<class S>
162 : requires ReadStream<S> && WriteStream<S> &&
163 : (!std::same_as<std::decay_t<S>, any_stream>)
164 4 : any_stream(S s)
165 4 : {
166 : struct guard {
167 : any_stream* self;
168 : void* ptr = nullptr;
169 : bool committed = false;
170 4 : ~guard() {
171 4 : if(!committed && ptr) {
172 : static_cast<S*>(ptr)->~S(); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
173 : ::operator delete(self->storage_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
174 : self->storage_ = nullptr; // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
175 : }
176 4 : }
177 4 : } g{this};
178 :
179 4 : storage_ = ::operator new(sizeof(S));
180 4 : S* ptr = ::new(storage_) S(std::move(s));
181 4 : g.ptr = ptr;
182 4 : stream_ptr_ = ptr;
183 8 : destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); };
184 :
185 : // Initialize bases with pointer (reference semantics)
186 4 : static_cast<any_read_stream&>(*this) = any_read_stream(ptr);
187 4 : static_cast<any_write_stream&>(*this) = any_write_stream(ptr);
188 :
189 4 : g.committed = true;
190 4 : }
191 :
192 : /** Construct by wrapping a bidirectional stream without ownership.
193 :
194 : Wraps the given stream by pointer. The stream must remain
195 : valid for the lifetime of this wrapper.
196 :
197 : @param s Pointer to the stream to wrap. Must satisfy both
198 : ReadStream and WriteStream concepts.
199 : */
200 : template<class S>
201 : requires ReadStream<S> && WriteStream<S>
202 32 : any_stream(S* s)
203 : : any_read_stream(s)
204 32 : , any_write_stream(s)
205 : {
206 : // storage_ remains nullptr - no ownership
207 32 : }
208 :
209 : /** Check if the wrapper contains a valid stream.
210 :
211 : Both bases must be valid for the wrapper to be valid.
212 :
213 : @return `true` if wrapping a stream, `false` if default-constructed
214 : or moved-from.
215 : */
216 : bool
217 12 : has_value() const noexcept
218 : {
219 19 : return any_read_stream::has_value() &&
220 19 : any_write_stream::has_value();
221 : }
222 :
223 : /** Check if the wrapper contains a valid stream.
224 :
225 : Both bases must be valid for the wrapper to be valid.
226 :
227 : @return `true` if wrapping a stream, `false` if default-constructed
228 : or moved-from.
229 : */
230 : explicit
231 2 : operator bool() const noexcept
232 : {
233 2 : return has_value();
234 : }
235 : };
236 :
237 : } // namespace capy
238 : } // namespace boost
239 :
240 : #endif
|