forked from Alepha/Alepha
456 lines
14 KiB
C++
456 lines
14 KiB
C++
static_assert( __cplusplus > 2020'99 );
|
|
|
|
#pragma once
|
|
|
|
#include <Alepha/Alepha.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <array>
|
|
#include <typeinfo>
|
|
#include <typeindex>
|
|
#include <exception>
|
|
#include <stdexcept>
|
|
|
|
#include <Alepha/lifetime.h>
|
|
#include <Alepha/Constness.h>
|
|
#include <Alepha/Capabilities.h>
|
|
#include <Alepha/assertion.h>
|
|
#include <Alepha/Concepts.h>
|
|
|
|
#include <Alepha/IOStreams/String.h>
|
|
|
|
|
|
namespace Alepha::Hydrogen ::detail:: Buffer_m
|
|
{
|
|
inline namespace exports {}
|
|
|
|
using namespace std::literals::string_literals;
|
|
using IOStreams::stringify;
|
|
|
|
namespace exports
|
|
{
|
|
class OutOfRangeError
|
|
: public virtual std::out_of_range
|
|
{
|
|
private:
|
|
const void *const baseAddress;
|
|
const std::size_t requestedSize;
|
|
const std::size_t availableSize;
|
|
|
|
protected:
|
|
~OutOfRangeError()= 0; // Make class abstract
|
|
|
|
explicit
|
|
OutOfRangeError( const void *const address, const std::size_t requestedSize, const std::size_t availableSize )
|
|
: baseAddress( address ), requestedSize( requestedSize ), availableSize( availableSize )
|
|
{}
|
|
|
|
public:
|
|
const void *getAddress() const noexcept { return baseAddress; }
|
|
const std::size_t getRequestedSize() const noexcept { return requestedSize; }
|
|
const std::size_t getAvailableSize() const noexcept { return availableSize; }
|
|
};
|
|
|
|
inline OutOfRangeError::~OutOfRangeError()= default;
|
|
|
|
class InsufficientSizeError
|
|
: virtual public OutOfRangeError
|
|
{
|
|
private:
|
|
const std::type_index typeID;
|
|
|
|
public:
|
|
explicit
|
|
InsufficientSizeError( const void *const location, const std::size_t requestedSize, const std::size_t availableSize, const std::type_index &type )
|
|
: std::out_of_range( "Tried to access an object of type "s + type.name() + " which is " + stringify( requestedSize ) + " bytes in size. "
|
|
+ "The request was at location " + stringify( location ) + " which only has " + stringify( availableSize )
|
|
+ " bytes allocated" ),
|
|
OutOfRangeError( location, requestedSize, availableSize ),
|
|
typeID( type )
|
|
{}
|
|
};
|
|
|
|
class OutOfRangeSizeError
|
|
: virtual public OutOfRangeError
|
|
{
|
|
public:
|
|
explicit
|
|
OutOfRangeSizeError( const void *const location, const std::ptrdiff_t requestedOffset, const std::size_t availableSpace )
|
|
: std::out_of_range( "Tried to view a byte offset of " + stringify( requestedOffset ) + " into location " + stringify( location )
|
|
+ " which is " + stringify( availableSpace ) + " bytes in size." ),
|
|
OutOfRangeError( location, requestedOffset, availableSpace )
|
|
{}
|
|
};
|
|
|
|
template< Constness constness > class Buffer;
|
|
template< typename Derived > class BufferModel;
|
|
|
|
constexpr Buffer< Mutable > copyData( Buffer< Mutable > destination, Buffer< Const > source );
|
|
|
|
constexpr void zeroData( Buffer< Mutable > buffer ) noexcept;
|
|
}
|
|
|
|
template< Constness constness >
|
|
class exports::Buffer
|
|
{
|
|
public:
|
|
using pointer_type= maybe_const_ptr_t< void, constness >;
|
|
using const_pointer_type= const void *;
|
|
|
|
using byte_pointer_type= maybe_const_ptr_t< std::byte, constness >;
|
|
using const_byte_pointer_type= const std::byte *;
|
|
|
|
private:
|
|
byte_pointer_type ptr= nullptr;
|
|
std::size_t bytes= 0;
|
|
|
|
public:
|
|
constexpr Buffer() noexcept= default;
|
|
|
|
constexpr
|
|
Buffer( const pointer_type ptr, const std::size_t bytes ) noexcept
|
|
: ptr( static_cast< byte_pointer_type >( ptr ) ), bytes( bytes )
|
|
{}
|
|
|
|
constexpr
|
|
Buffer( const Buffer< Mutable > © ) noexcept
|
|
: ptr( copy.byte_data() ), bytes( copy.size() ) {}
|
|
|
|
template< Constness constness_= constness >
|
|
requires( constness_ == Mutable )
|
|
constexpr
|
|
Buffer( const Buffer< Const > © ) noexcept = delete;
|
|
|
|
|
|
constexpr byte_pointer_type byte_data() const noexcept { return ptr; }
|
|
constexpr pointer_type data() const noexcept { return ptr; }
|
|
|
|
constexpr const_byte_pointer_type const_byte_data() const noexcept { return ptr; }
|
|
constexpr const_pointer_type const_data() const noexcept { return ptr; }
|
|
|
|
constexpr std::size_t size() const noexcept { return bytes; }
|
|
constexpr bool empty() const noexcept { return size() == 0; }
|
|
|
|
constexpr byte_pointer_type begin() const noexcept { return byte_data(); }
|
|
constexpr byte_pointer_type end() const noexcept { return begin() + size(); }
|
|
|
|
constexpr const_byte_pointer_type cbegin() const noexcept { return begin(); }
|
|
constexpr const_byte_pointer_type cend() const noexcept { return end(); }
|
|
|
|
template< typename T > void operator[]( T ) const= delete;
|
|
template< typename T > void operator[]( T )= delete;
|
|
|
|
template< typename T >
|
|
constexpr std::add_lvalue_reference_t< maybe_const_t< T, constness > >
|
|
as( std::nothrow_t ) const noexcept
|
|
{
|
|
assertion( sizeof( T ) <= bytes );
|
|
return *Alepha::start_lifetime_as< std::add_lvalue_reference_t< maybe_const_t< T, constness > > >( ptr );
|
|
}
|
|
|
|
template< typename T >
|
|
constexpr maybe_const_t< T &, constness >
|
|
as() const
|
|
{
|
|
if( sizeof( T ) > bytes ) throw InsufficientSizeError{ ptr, sizeof( T ), bytes, typeid( T ) };
|
|
return this->as< std::add_lvalue_reference_t< maybe_const_t< T, constness > > >( std::nothrow );
|
|
}
|
|
|
|
template< typename T >
|
|
constexpr std::add_lvalue_reference_t< std::add_const_t< T & > >
|
|
const_as( std::nothrow_t ) const noexcept
|
|
{
|
|
assertion( sizeof( T ) <= bytes );
|
|
return *Alepha::start_lifetime_as< std::add_const_t< T > >( ptr );
|
|
}
|
|
|
|
template< typename T >
|
|
constexpr std::add_lvalue_reference_t< std::add_const_t< T > >
|
|
const_as() const
|
|
{
|
|
if( sizeof( T ) > bytes ) throw InsufficientSizeError{ ptr, sizeof( T ), bytes, typeid( const T ) };
|
|
return this->const_as< T >( std::nothrow );
|
|
}
|
|
|
|
constexpr operator pointer_type () const noexcept { return ptr; }
|
|
|
|
/*!
|
|
* Advance the view of this `Buffer` object.
|
|
*
|
|
* Because `Buffer` objects model a pointer to a block of data which is aware of
|
|
* the size of that block, advancing that pointer should permit a view of the remainder
|
|
* of that block.
|
|
*
|
|
* A common technique in working with such blocks is to have to advance a pointer and
|
|
* decrease a size count. This operator does both actions in one semantic step.
|
|
*
|
|
* @note Behaves the same as `window= window + amount`.
|
|
*
|
|
* Example use case:
|
|
*
|
|
* ```
|
|
* AutoRAII targetFile{[]{ return ::fopen( "output.dat", "wb" ); }, fclose };
|
|
* Buffer< Const > myBuf= getSomeBufferFromSomewhere();
|
|
* while( not myBuf.empty() )
|
|
* {
|
|
* const auto amtWritten= fwrite( myBuf, myBuf.size(), 1, targetFile );
|
|
* myBuf+= amtWritten;
|
|
* }
|
|
* ```
|
|
*
|
|
* In this example, the code walks through the buffer pointed to by `myBuf`. It uses `myBuf`
|
|
* as a "smart pointer" which knows the end of its range.
|
|
*/
|
|
Buffer &
|
|
operator+= ( const std::size_t amount )
|
|
{
|
|
if( amount > bytes ) throw OutOfRangeSizeError( ptr, amount, bytes );
|
|
|
|
ptr+= amount;
|
|
bytes-= amount;
|
|
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
template< Constness constness >
|
|
constexpr auto
|
|
cbegin( const Buffer< constness > &buffer ) noexcept
|
|
{
|
|
return buffer.cbegin();
|
|
}
|
|
|
|
template< Constness constness >
|
|
constexpr auto
|
|
cend( const Buffer< constness > &buffer ) noexcept
|
|
{
|
|
return buffer.cend();
|
|
}
|
|
|
|
struct BufferModel_capability {};
|
|
|
|
template< typename T >
|
|
concept UndecayedBufferModelable= HasCapability< T, BufferModel_capability >;
|
|
|
|
template< typename T >
|
|
concept BufferModelable= UndecayedBufferModelable< std::decay_t< T > >;
|
|
|
|
template< typename Derived >
|
|
class exports::BufferModel : public BufferModel_capability
|
|
{
|
|
private:
|
|
constexpr auto &crtp() noexcept { return static_cast< Derived & >( *this ); }
|
|
constexpr const auto &crtp() const noexcept { return static_cast< const Derived & >( *this ); }
|
|
|
|
constexpr auto buffer() { return static_cast< Buffer< Mutable > >( crtp() ); }
|
|
constexpr auto buffer() const { return static_cast< Buffer< Const > >( crtp() ); }
|
|
|
|
public:
|
|
constexpr auto byte_data() { return buffer().byte_data(); }
|
|
constexpr const auto byte_data() const { return buffer().byte_data(); }
|
|
|
|
constexpr auto data() { return buffer().data(); }
|
|
constexpr const auto data() const { return buffer().data(); }
|
|
|
|
constexpr decltype( auto ) cbegin() const { return buffer().cbegin(); }
|
|
constexpr decltype( auto ) cend() const { return buffer().cend(); }
|
|
|
|
constexpr decltype( auto ) begin() const { return buffer().begin(); }
|
|
constexpr decltype( auto ) end() const { return buffer().end(); }
|
|
|
|
constexpr decltype( auto ) begin() { return buffer().begin(); }
|
|
constexpr decltype( auto ) end() { return buffer().end(); }
|
|
|
|
constexpr std::size_t size() const { return buffer().size(); }
|
|
|
|
constexpr operator void *() { return buffer(); }
|
|
constexpr operator const void *() { return buffer(); }
|
|
|
|
constexpr operator const void *() const { return buffer(); }
|
|
|
|
template< typename T > void operator[]( T ) const= delete;
|
|
template< typename T > void operator[]( T )= delete;
|
|
|
|
template< typename T > constexpr decltype( auto ) as() const { return buffer().template as< T >(); }
|
|
template< typename T > constexpr decltype( auto ) as() { return buffer().template as< T >(); }
|
|
|
|
template< typename T > constexpr decltype( auto ) const_as() const { return buffer().template const_as< T >(); }
|
|
template< typename T > constexpr decltype( auto ) const_as() { return buffer().template const_as< T >(); }
|
|
};
|
|
|
|
template< typename T >
|
|
extern Constness constness_of_v;
|
|
|
|
template< UndecayedBufferModelable T >
|
|
constexpr Constness constness_of_v< T >{ std::is_const_v< T > };
|
|
|
|
template< Constness constness >
|
|
constexpr Constness constness_of_v< Buffer< constness > >{ constness };
|
|
|
|
template< Constness constness >
|
|
constexpr auto
|
|
operator + ( const Buffer< constness > buffer, const std::size_t offset )
|
|
{
|
|
if( offset > buffer.size() ) throw OutOfRangeSizeError{ buffer.data(), std::ptrdiff_t( offset ), buffer.size() };
|
|
return Buffer< constness >{ buffer.byte_data() + offset, buffer.size() - offset };
|
|
}
|
|
|
|
template< Constness constness >
|
|
constexpr auto
|
|
operator + ( const std::size_t offset, const Buffer< constness > buffer )
|
|
{
|
|
return buffer + offset;
|
|
}
|
|
|
|
|
|
// Compute arbitrary offsets with `BufferModel` derivatives.
|
|
template< BufferModelable Type >
|
|
constexpr auto
|
|
operator + ( Type &&item, const std::size_t offset )
|
|
{
|
|
return static_cast< Buffer< constness_of_v< std::remove_reference_t< Type > > > >( item ) + offset;
|
|
}
|
|
|
|
constexpr auto
|
|
operator + ( const std::size_t offset, BufferModelable auto &&item )
|
|
{
|
|
return item + offset;
|
|
}
|
|
|
|
constexpr Buffer< Mutable >
|
|
make_buffer( Concepts::StandardLayoutAggregate auto &aggregate ) noexcept
|
|
{
|
|
return { &aggregate, sizeof( aggregate ) };
|
|
}
|
|
|
|
constexpr Buffer< Const >
|
|
make_buffer( const Concepts::StandardLayoutAggregate auto &aggregate ) noexcept
|
|
{
|
|
return { &aggregate, sizeof( aggregate ) };
|
|
}
|
|
|
|
template< Concepts::StandardLayout T >
|
|
constexpr Buffer< Mutable >
|
|
make_buffer( std::vector< T > &vector ) noexcept
|
|
{
|
|
// TODO: Do we need to consider overflow here?
|
|
return { vector.data(), vector.size() * sizeof( T ) };
|
|
}
|
|
|
|
template< Concepts::StandardLayout T >
|
|
constexpr Buffer< Const >
|
|
make_buffer( const std::vector< T > &vector ) noexcept
|
|
{
|
|
// TODO: Do we need to consider overflow here?
|
|
return { vector.data(), vector.size() * sizeof( T ) };
|
|
}
|
|
|
|
|
|
template< Concepts::StandardLayout T, std::size_t size >
|
|
constexpr Buffer< Mutable >
|
|
make_buffer( std::array< T, size > &array ) noexcept
|
|
{
|
|
// TODO: Do we need to consider overflow here?
|
|
return { array.data(), sizeof( array ) };
|
|
}
|
|
|
|
template< Concepts::StandardLayout T, std::size_t size >
|
|
constexpr Buffer< Const >
|
|
make_buffer( const std::array< T, size > &array ) noexcept
|
|
{
|
|
// TODO: Do we need to consider overflow here?
|
|
return { array.data(), sizeof( array ) };
|
|
}
|
|
|
|
|
|
template< Concepts::StandardLayout T, std::size_t size >
|
|
constexpr Buffer< Mutable >
|
|
make_buffer( T array[ size ] ) noexcept
|
|
{
|
|
// TODO: Do we need to consider overflow here?
|
|
return { array, sizeof( array ) };
|
|
}
|
|
|
|
template< Concepts::StandardLayout T, std::size_t size >
|
|
constexpr Buffer< Const >
|
|
make_buffer( const T array[ size ] ) noexcept
|
|
{
|
|
// TODO: Do we need to consider overflow here?
|
|
return { array, sizeof( array ) };
|
|
}
|
|
|
|
|
|
inline Buffer< Mutable >
|
|
make_buffer( std::string &string ) noexcept
|
|
{
|
|
return { string.data(), string.size() };
|
|
}
|
|
|
|
inline Buffer< Const >
|
|
make_buffer( const std::string &string ) noexcept
|
|
{
|
|
return { string.data(), string.size() };
|
|
}
|
|
|
|
|
|
constexpr Buffer< Mutable >
|
|
exports::copyData( const Buffer< Mutable > destination, const Buffer< Const > source )
|
|
{
|
|
if( source.size() > destination.size() ) throw InsufficientSizeError{ destination.data(), source.size(), destination.size(), typeid( std::byte ) };
|
|
|
|
std::memcpy( destination, source, source.size() );
|
|
return { destination, source.size() };
|
|
}
|
|
|
|
constexpr void
|
|
exports::zeroData( const Buffer< Mutable > buffer ) noexcept
|
|
{
|
|
::memset( buffer, 0, buffer.size() );
|
|
}
|
|
|
|
namespace exports
|
|
{
|
|
using detail::Buffer_m::make_buffer;
|
|
}
|
|
}
|
|
|
|
namespace Alepha::Hydrogen::inline exports::inline Buffer_m
|
|
{
|
|
using namespace detail::Buffer_m::exports;
|
|
}
|
|
|
|
/*
|
|
* It is not possible to explicitly specialize `std::cbegin` and `std::cend` with differing results than what they
|
|
* normally return (`decltype( std::as_const( range ).begin() )`), therefore the best we can do is just delete
|
|
* them, in the interest of preserving as much correctness as we can.
|
|
*
|
|
* This really isn't a problem, anyway, as `cbegin` and `cend` are meant to be ADL-found aspect-functions, not
|
|
* explictly called from `std::`, just like `swap`.
|
|
*
|
|
* Correct:
|
|
* ```
|
|
* using std::cbegin, std::cend;
|
|
* std::sort( cbegin( list ), cend( list ) );
|
|
* ```
|
|
*
|
|
* Incorrect:
|
|
* ```
|
|
* std::sort( std::cbegin( list ), std::cend( list ) );
|
|
* ```
|
|
*
|
|
* Because of the below deletion and the above correct/incorrect examples, it really is not a problem that they're
|
|
* deleted. In fact, it's a good thing. It will help catch incorrect usage in your code.
|
|
*/
|
|
|
|
template<>
|
|
constexpr auto
|
|
std::cbegin( const ::Alepha::Hydrogen::Buffer< Alepha::Hydrogen::Mutable > &range ) -> decltype( range.begin() )= delete;
|
|
|
|
template<>
|
|
constexpr auto
|
|
std::cend( const ::Alepha::Hydrogen::Buffer< Alepha::Hydrogen::Mutable > &range ) -> decltype( range.end() )= delete;
|