This describes type util::any<std::size_t size, std::size_t align>
- an implementation of std::any
with a static size/no dynamic allocation. It closely follows the standard's specification for std::any
.
A discriminated type that can contain values of different types but does not attempt conversion between them, i.e. 5 is held strictly as an int and is not implicitly convertible either to "5" or to 5.0. This indifference to interpretation but awareness of type effectively allows safe, generic containers of single values, with no scope for surprises from ambiguous conversions.
[Taken from the standard's description of std::any
]
Review goals
Correctness: The lifetime of objects is managed manually. Are there any invalid states in my code? Can invalid states be generated through usage?
Optimization: There is a small size overhead required in order to enable type-erasure. Is there a way to make this code faster or require less memory without decreasing performance?
Design: Any design suggestions/comments are welcome. Most constraints are done through
static_assert
, this generates an informative error message, but does not highlight the actual error location; would it be better to simply remove functions throughstd::enable_if
?
Usage
Instances of util::any<std::size_t, std::size_t>
can hold any type that fits in the specified amount of static memory it has been created with. Access of the contained value is done through the static_any_cast<T>
free functions.
Example
#include <cassert>
#include "static_any.h"
int main()
{
util::static_any<4, 4> aa; // holds any type with a size <= 4, aligned as 4
assert( aa.empty() );
aa = 'A'; // holds a char
aa = "abc"; // now holds char const[4];
//aa = "abcd"; // error: char const[5] is too big.
aa = 3.0f; // now holds a float
try
{
util::static_any_cast<int>( aa ); // invalid cast; aa holds a float
}
catch ( util::bad_static_any_cast const& )
{}
assert( util::static_any_cast<float const&>( aa ) == 3.0f ); // view through const-ref
util::static_any_cast<float&>( aa ) = 5.0f; // modify through reference cast
assert( util::static_any_cast<float>( aa ) == 5.0f );
}
Implementation
All relevant parts of the code are commented. This is all in one file, but I've separated into sections for ease of review.
#ifndef UTIL_STATIC_ANY_H
#define UTIL_STATIC_ANY_H
#include <new>
#include <typeinfo>
#include <type_traits>
Implementation - class bad_static_any_cast
namespace util
{
class bad_static_any_cast : public std::bad_cast
{
virtual char const* what() const noexcept override
{
return "bad_static_any_cast: casting to invalid type";
}
};
}
Implementation - class static_any
namespace util
{
template <std::size_t size, std::size_t align = 8>
class static_any;
template <class StaticAny>
struct is_static_any : std::false_type {};
template <std::size_t size, std::size_t align>
struct is_static_any<static_any<size, align>> : std::true_type {};
template <std::size_t size, std::size_t align>
class static_any
{
public:
// [ destructor ]
~static_any()
{
if ( m_has_value )
{
storage_base_ptr()->destroy_contained_value();
}
}
// [ constructors ]
/**
* effects: constructs an empty static_any.
* postcondition: *this does not have a contained value.
*/
static_any() noexcept
: m_has_value{ false }
{
}
/**
* effects: constructs a static_any from another static_any by copying its
contained value.
* requirements: the argument static_any must be smaller or equal in size.
* throws: any exception thrown by the copy constructor of the contained value.
*/
template <std::size_t other_size, std::size_t other_align>
static_any( static_any<other_size, other_align> const& other )
: m_has_value{ other.m_has_value }
{
static_assert( other_size <= size,
"static_any error: cannot be constructed from a larger static_any" );
if ( m_has_value )
{
other.storage_base_ptr()->copy_to( &m_memory );
}
}
/**
* effects: constructs a static_any from another static_any by moving its
contained value.
* requires: the argument static_any must be smaller or equal in size.
* throws: any exception thrown by the move constructor of the contained value.
*/
template <std::size_t other_size, std::size_t other_align>
static_any( static_any<other_size, other_align>&& other )
: m_has_value{ other.m_has_value }
{
static_assert( other_size <= size,
"static_any error: cannot be constructed from a larger static_any" );
if ( m_has_value )
{
other.storage_base_ptr()->move_to( &m_memory );
other.m_has_value = false;
}
}
/**
* effects: constructs a static_any containing a direct-initialized value of
type std::decay_t<T>.
* requires: type std::decay_t<T> must be copy constructible.
* throws: any exceptions thrown by the selected constructor of std::decay_t<T>.
*/
template
<
class T,
std::enable_if_t<!is_static_any<std::decay_t<T>>::value, int> = 0
>
static_any( T&& value )
: m_has_value{ true }
{
static_assert( sizeof( T ) <= size,
"incompatible type size; requires sizeof(T) <= size" );
static_assert( std::is_copy_constructible<std::decay<T>>::value,
"std::decay_t<T> must be copy constructible" );
::new ( &m_memory ) storage<std::decay_t<T>>( std::forward<T>( value ) );
}
// [ assignment ]
/**
* effects: static_any{rhs}.swap(*this);
no effects if an exception is thrown.
* requires: the argument static_any must be smaller or equal in size.
* returns: *this.
* throws: any exception thrown by the copy constructor of the contained
value of the argument static_any.
*/
template <std::size_t other_size, std::size_t other_align>
static_any& operator=( static_any<other_size, other_align> const& rhs )
{
static_any{ rhs }.swap( *this );
return *this;
}
/**
* effects: static_any{std::move(rhs)}.swap(*this);
no effects if an exception is thrown.
* requires: the argument static_any must be smaller or equal in size.
* returns: *this.
* throws: any exception thrown by the move constructor of the contained
value of the argument static_any.
*/
template <std::size_t other_size, std::size_t other_align>
static_any& operator=( static_any<other_size, other_align>&& rhs )
{
static_any{ std::move( rhs ) }.swap( *this );
return *this;
}
/**
* effects: static_any{std::forward<T>(rhs)}.swap(*this);
no effects if an exception is thrown.
* requires: type std::decay_t<T> must be copy constructible.
* returns: *this.
* throws: any exception thrown by the selected constructor of std::decay_t<T>.
*/
template
<
class T,
std::enable_if_t<!is_static_any<std::decay_t<T>>::value, int> = 0
>
static_any& operator=( T&& value )
{
static_any{ std::forward<T>( value ) }.swap( *this );
return *this;
}
// [ modifiers ]
/**
* effects: if *this has a contained value, destroy the contained value.
* postcondition: *this does not have a contained value.
*/
void clear()
{
if ( m_has_value )
{
storage_base_ptr()->destroy_contained_value();
m_has_value = false;
}
}
/**
* effects: exchanges the contained values of *this and argument other.
*/
void swap( static_any& other )
{
if ( this != &other )
{
if ( m_has_value && other.m_has_value )
{
other.unsafe_swap( *this );
}
else if ( m_has_value )
{
storage_base* storage_ptr{ storage_base_ptr() };
storage_ptr->move_to( &other.m_memory );
other.m_has_value = true;
storage_ptr->destroy_contained_value();
m_has_value = false;
}
else if ( other.m_has_value )
{
storage_base* other_storage_ptr{ other.storage_base_ptr() };
other_storage_ptr->move_to( &m_memory );
m_has_value = true;
other_storage_ptr->destroy_contained_value();
other.m_has_value = false;
}
}
}
/**
* effects: exchanges the contained values of *this and argument other without
checking for valid state (non-empty instances, self-assign, etc.).
*/
template <std::size_t other_size, std::size_t other_align>
void unsafe_swap( static_any<other_size, other_align>& other )
{
memory_t tmp;
storage_base* storage_ptr{ storage_base_ptr() };
storage_ptr->move_to( &tmp );
storage_ptr->destroy_contained_value();
storage_base* other_storage_ptr{ other.storage_base_ptr() };
other_storage_ptr->move_to( &m_memory );
other_storage_ptr->destroy_contained_value();
reinterpret_cast<storage_base*>( &tmp )->move_to( &other.m_memory );
}
// [ observers ]
/**
* effects: indicates whether the static_any has a contained value or not.
* returns: true if *this has no contained value, otherwise false.
*/
bool empty() const noexcept
{
return !m_has_value;
}
/**
* effects: accesses the std::type_info of the associated contained value.
* returns: typeid(T) if *this has a contained value, otherwise typeid(void).
*/
std::type_info const& type() const noexcept
{
static std::type_info const& void_type_info{ typeid( void ) };
return m_has_value ? storage_base_ptr()->type() : void_type_info;
}
private:
// [ friend declarations ]
template <std::size_t other_size, std::size_t other_align>
friend class static_any;
template <class T, std::size_t size, std::size_t align>
friend T static_any_cast( static_any<size, align>& operand );
template <class T, std::size_t size, std::size_t align>
friend T static_any_cast( static_any<size, align> const& operand );
template <class T, std::size_t size, std::size_t align>
friend T static_any_cast( static_any<size, align>&& operand );
template <class T, std::size_t size, std::size_t align>
friend T* static_any_cast( static_any<size, align>* operand ) noexcept;
template <class T, std::size_t size, std::size_t align>
friend T const* static_any_cast( static_any<size, align> const* operand ) noexcept;
// [ helper types ]
/**
* base storage class
*/
class storage_base
{
public:
virtual ~storage_base() = default;
storage_base( std::type_info const& type_info )
: m_type_info{ type_info }
{}
virtual void copy_to( void* p_location ) const = 0;
virtual void move_to( void* p_location ) = 0;
virtual void destroy_contained_value() = 0;
std::type_info const& type() const noexcept
{
return m_type_info;
}
private:
std::type_info const& m_type_info;
};
/**
* helper type used to determine the maximum size of *this' storage<T>
*/
class storage_size_t : public storage_base
{
char filler[ size ];
};
/**
* wrapper type to be created in static uninitialized memory
* the contained value must be freed with a call to destroy_contained_value()
*/
template <class T>
class storage : public storage_base
{
public:
template <class U>
storage( U&& value )
noexcept( std::is_nothrow_constructible<T, U&&>::value )
: storage_base( typeid( T ) )
, m_value( std::forward<U>( value ) )
{
}
storage( storage const& ) = delete;
storage( storage&& ) = delete;
storage& operator=( storage const& ) = delete;
storage& operator=( storage&& ) = delete;
void copy_to( void* p_location ) const override
{
::new ( p_location ) storage( m_value );
}
void move_to( void* p_location ) override
{
::new ( p_location ) storage( std::move( m_value ) );
}
void destroy_contained_value() override
{
m_value.~T();
}
T m_value;
private:
~storage() = default;
};
storage_base* storage_base_ptr()
{
return reinterpret_cast<storage_base*>( &m_memory );
}
storage_base const* storage_base_ptr() const
{
return reinterpret_cast<storage_base const*>( &m_memory );
}
using memory_t = std::aligned_storage_t<sizeof( storage_size_t ), align>;
memory_t m_memory;
bool m_has_value;
};
}
Implementation - free functions
namespace util
{
// [ non-member functions ]
/**
* effects: x.swap( y ).
*/
template <std::size_t size, std::size_t align>
void swap( static_any<size, align>& x, static_any<size, align>& y ) noexcept
{
x.swap( y );
}
/**
* requires: std::is_reference<T>::value && std::is_copy_constructible<T>::value.
* returns: *static_any_cast<std::remove_reference_t<T>>( &operand ).
* throws: bad_static_any_cast if operand.type() != typeid( std::remove_reference_t<T> ).
*/
template <class T, std::size_t size, std::size_t align>
T static_any_cast( static_any<size, align>& operand )
{
static_assert(
std::is_reference<T>::value || std::is_copy_constructible<T>::value,
"any_cast requirement not met: T is a reference or copy constructible" );
auto ret{ static_any_cast<std::remove_reference_t<T>>( &operand ) };
if ( ret )
{
return *ret;
}
throw bad_static_any_cast{};
}
/**
* requires: std::is_reference<T>::value && std::is_copy_constructible<T>::value.
* returns: *static_any_cast<std::add_const_t<std::remove_reference_t<T>>>(&operand).
* throws: bad_static_any_cast if operand.type() != typeid(std::remove_reference_t<T>).
*/
template <class T, std::size_t size, std::size_t align>
T static_any_cast( static_any<size, align> const& operand )
{
static_assert(
std::is_reference<T>::value || std::is_copy_constructible<T>::value,
"any_cast requirement not met: T is a reference or copy constructible" );
auto ret
{
static_any_cast<std::add_const_t<std::remove_reference_t<T>>>( &operand )
};
if ( ret )
{
return *ret;
}
throw bad_static_any_cast{};
}
/**
* requires: std::is_reference<T>::value && std::is_copy_constructible<T>::value.
* returns: *static_any_cast<std::remove_reference_t<T>>(&operand).
* throws: bad_static_any_cast if operand.type() != typeid(std::remove_reference_t<T>).
*/
template <class T, std::size_t size, std::size_t align>
T static_any_cast( static_any<size, align>&& operand )
{
static_assert(
std::is_reference<T>::value || std::is_copy_constructible<T>::value,
"any_cast requirement not met: T is a reference or copy constructible" );
auto ret{ static_any_cast<std::remove_reference_t<T>>( &operand ) };
if ( ret )
{
return *ret;
}
throw bad_static_any_cast{};
}
/**
* returns: if operand != nullptr && operand->type() == typeid(T),
a pointer to the object contained by operand, otherwise nullptr.
*/
template <class T, std::size_t size, std::size_t align>
T* static_any_cast( static_any<size, align>* operand ) noexcept
{
static std::type_info const& typeid_value = typeid( std::remove_reference_t<T> );
using pointer = static_any<size, align>::template storage<T>*;
return operand && operand->type() == typeid_value ?
&static_cast<pointer>( operand->storage_base_ptr() )->m_value : nullptr;
}
/**
* returns: if operand != nullptr && operand->type() == typeid(T),
a pointer to const of the object contained by operand, otherwise nullptr.
*/
template <class T, std::size_t size, std::size_t align>
T const* static_any_cast( static_any<size, align> const* operand ) noexcept
{
static std::type_info const& typeid_value = typeid( std::remove_reference_t<T> );
using pointer = static_any<size, align>::template storage<T> const*;
return operand && operand->type() == typeid_value ?
&static_cast<pointer>( operand->storage_base_ptr() )->m_value : nullptr;
}
}
#endif // UTIL_STATIC_ANY_H