LCOV - code coverage report
Current view: top level - capy/io - any_stream.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 46 46
Test Date: 2026-08-14 20:51:18 Functions: 100.0 % 14 14

           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
        

Generated by: LCOV version 2.3