LCOV - code coverage report
Current view: top level - boost/capy/io - any_stream.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 89.8 % 49 44
Test Date: 2026-01-30 23:43:15 Functions: 100.0 % 9 9

            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
        

Generated by: LCOV version 2.3