density
C++11 library for paged memory management, function queues, heterogeneous queues and lifo memory management
Public Types | Public Member Functions | Static Public Member Functions | Friends | List of all members
runtime_type< FEATURES > Class Template Reference

#include <runtime_type.h>

Public Types

using feature_list_type = typename std::conditional< (sizeof...(FEATURES) > 0), feature_list< FEATURES... >, default_type_features >::type
 
using tuple_type = typename feature_list_type::tuple_type
 

Public Member Functions

constexpr runtime_type () noexcept=default
 
template<typename... OTHER_FEATURES>
constexpr runtime_type (const runtime_type< OTHER_FEATURES... > &i_source) noexcept
 
template<typename... OTHER_FEATURES>
DENSITY_CPP14_CONSTEXPR runtime_typeoperator= (const runtime_type< OTHER_FEATURES... > &i_source) noexcept
 
constexpr bool empty () const noexcept
 
DENSITY_CPP14_CONSTEXPR void clear () noexcept
 
constexpr size_t size () const noexcept
 
constexpr size_t alignment () const noexcept
 
void default_construct (void *i_dest) const
 
void copy_construct (void *i_dest, const void *i_source) const
 
void move_construct (void *i_dest, void *i_source) const
 
void destroy (void *i_dest) const noexcept
 
const std::type_info & type_info () const noexcept
 
bool are_equal (const void *i_first, const void *i_second) const noexcept
 
template<typename FEATURE >
const FEATURE & get_feature () const noexcept
 
constexpr bool operator== (const runtime_type &i_other) const noexcept
 
constexpr bool operator!= (const runtime_type &i_other) const noexcept
 
template<typename TARGET_TYPE >
constexpr bool is () const noexcept
 

Static Public Member Functions

template<typename TARGET_TYPE >
static constexpr runtime_type make () noexcept
 

Friends

void swap (runtime_type &i_first, runtime_type &i_second) noexcept
 

Detailed Description

template<typename... FEATURES>
class density::runtime_type< FEATURES >

Class template that performs type-erasure. A runtime_type can be empty, or can be bound to a target type, from which it captures and exposes the type features specified by the template argument list. It is copyable and trivially destructible. Specializations of runtime_type satisfy the requirements of RuntimeType.

Template Parameters
FEATURES...list of features to be captures from the target type.

Implementation note: runtime_type is actually a wrapper around a pointer to a static constexpr instance of the tuple of the supported features. It is actually a generalization of the pointer to the v-table in a polymorphic type.

Like in a feature_list, each type in the template arguments pack is either:

Furthermore duplicate features are removed (only the first occurrence of a feature is considered). If after all these transformations two runtime_type specializations have the same feature_list, they are copyable and assignable each others:

using RuntimeType_1 = runtime_type<f_size, f_alignment>;
using RuntimeType_2 = runtime_type<f_size, f_none, f_size, f_alignment>;
using RuntimeType_3 = runtime_type<feature_list<f_size, feature_list<f_none>>, f_alignment, f_alignment>;
RuntimeType_1 a;
RuntimeType_2 b = a;
RuntimeType_3 c = b;

This example shows a very simple usage of runtime_type:

// we just want to create, print and destroy objects
using RuntimeType =
runtime_type<f_size, f_alignment, f_ostream, f_default_construct, f_destroy>;
// create a runtime type bound to std::complex<float> (the target type)
auto const type = RuntimeType::make<std::complex<float>>();
/* From now on, we can manage instances of the target type just using the runtime_type.
Note that this is a kind of generic code different from C++ templates, because the
type is bound at runtime.
It's also very low-level code, and for simplicity it's not exception-safe. */
// allocate and default construct an object
void * const buff = aligned_allocate(type.size(), type.alignment());
type.default_construct(buff); /* equivalent to get_feature<f_default_construct>()(buff).
default_construct is just a convenience function. */
// now print the object std::cout
type.get_feature<f_ostream>()(std::cout, buff); /* There is no convenience function to
do that, use get_feature. */
/* destroy and deallocate. */
type.destroy(buff);
aligned_deallocate(buff, type.size(), type.alignment());

Every type feature implies one or more concepts. For example f_equal implies EqualityComparable. If a target type does not satisfy the syntactic requirements of all the features supported by the runtime_type, the function make fails to specialize, and a compile error is reported. For example we can't bind an instance of the RuntimeType of the example above to std::lock_guard, because RuntimeType has the feature f_default_construct, but std::lock_guard is not default constructible.

Any type feature can be retrieved with the member function template get_feature. Anyway a set of convenience member function is avaiable for the most common features: size, alignment, default_construct, copy_construct, move_construct, destroy, type_info, are_equal.

Managing instances of the target type directly is difficult and requires very low-level code: instances are handled by void pointers, they must explictly allocated, constructed, destroyed and deallocated. Indeed runtime_type is not intended to be used directly in this way, but it should instead used by library code to provide high-level features.

In the following example runtime_type is used to implement a class template very similar to std::any, which can be customized specifying which features must be captured from the underlying object.

template <typename... FEATURES> class any
{
public:
using runtime_type = density::runtime_type<FEATURES...>;
using tuple_type = typename feature_list_type::tuple_type;
constexpr any() noexcept = default;
template <typename TYPE>
any(const TYPE & i_source) : m_type(runtime_type::template make<TYPE>())
{
allocate();
deallocate_if_excepts([&] { m_type.copy_construct(m_object, &i_source); });
}
any(const any & i_source) : m_type(i_source.m_type)
{
allocate();
deallocate_if_excepts([&] { m_type.copy_construct(m_object, i_source.m_object); });
}
any(any && i_source) noexcept : m_type(i_source.m_type), m_object(i_source.m_object)
{
i_source.m_object = nullptr;
i_source.m_type.clear();
}
template <typename CONSTRUCTION_FUNC>
any(const runtime_type & i_type, CONSTRUCTION_FUNC && i_construction_func) : m_type(i_type)
{
allocate();
deallocate_if_excepts(
[&] { std::forward<CONSTRUCTION_FUNC>(i_construction_func)(m_object); });
}
any & operator=(any i_source) noexcept
{
swap(*this, i_source);
return *this;
}
~any()
{
if (m_object != nullptr)
{
m_type.destroy(m_object);
deallocate();
}
}
friend void swap(any & i_first, any & i_second) noexcept
{
std::swap(i_first.m_type, i_second.m_type);
std::swap(i_first.m_object, i_second.m_object);
}
bool has_value() const noexcept { return m_object != nullptr; }
const std::type_info & type() const noexcept
{
return m_type.empty() ? typeid(void) : m_type.type_info();
}
template <typename DEST_TYPE> friend DEST_TYPE any_cast(const any & i_source)
{
if (i_source.m_type.template is<DEST_TYPE>())
return *static_cast<DEST_TYPE *>(i_source.m_object);
else
throw bad_any_cast();
}
template <typename DEST_TYPE> friend DEST_TYPE any_cast(any && i_source)
{
if (i_source.m_type.template is<DEST_TYPE>())
return std::move(*static_cast<DEST_TYPE *>(i_source.m_object));
else
throw bad_any_cast();
}
template <typename DEST_TYPE>
friend const DEST_TYPE * any_cast(const any * i_source) noexcept
{
if (i_source->m_type.template is<DEST_TYPE>())
return static_cast<const DEST_TYPE *>(i_source->m_object);
else
return nullptr;
}
template <typename DEST_TYPE> friend DEST_TYPE * any_cast(any * i_source) noexcept
{
if (i_source->m_type.template is<DEST_TYPE>())
return static_cast<DEST_TYPE *>(i_source->m_object);
else
return nullptr;
}
bool operator==(const any & i_source) const noexcept
{
if (m_type != i_source.m_type)
return false;
return m_object == nullptr || m_type.are_equal(m_object, i_source.m_object);
}
bool operator!=(const any & i_source) const noexcept { return !operator==(i_source); }
template <typename FEATURE> const FEATURE & get_type_feature() const noexcept
{
return m_type.template get_feature<FEATURE>();
}
void * object_ptr() noexcept { return m_object; }
const void * object_ptr() const noexcept { return m_object; }
const runtime_type & get_runtime_type() const noexcept { return m_type; }
private:
void allocate() { m_object = density::aligned_allocate(m_type.size(), m_type.alignment()); }
void deallocate() noexcept
{
density::aligned_deallocate(m_object, m_type.size(), m_type.alignment());
}
template <typename FUNC> void deallocate_if_excepts(const FUNC & i_func)
{
try
{
i_func();
}
catch (...)
{
if (m_object != nullptr)
deallocate();
m_type.clear();
throw;
}
}
private:
runtime_type m_type;
void * m_object{nullptr};
};
// any_cast is already defined, but we have to declare it too
template <typename DEST_TYPE, typename... FEATURES>
DEST_TYPE any_cast(const any<FEATURES...> & i_source);
template <typename DEST_TYPE, typename... FEATURES>
DEST_TYPE any_cast(any<FEATURES...> && i_source);
template <typename DEST_TYPE, typename... FEATURES>
const DEST_TYPE * any_cast(const any<FEATURES...> * i_source) noexcept;
template <typename DEST_TYPE, typename... FEATURES>
DEST_TYPE * any_cast(any<FEATURES...> * i_source) noexcept;

This example shows how a non-intrusive serialization operator << for the above any can be easly implemented exploiting the feature built-in f_ostream:

template <typename... FEATURES>
std::ostream & operator<<(std::ostream & i_dest, const any<FEATURES...> & i_any)
{
using namespace density;
static_assert( // for simplicity we don't SFINAE
"The provided any leaks the feature f_ostream");
if (i_any.has_value())
i_any.template get_type_feature<f_ostream>()(i_dest, i_any.object_ptr());
else
i_dest << "[empty]";
return i_dest;
}

This example defines the feature f_sum and then overloads the operator operator + between two any's

struct f_sum
{
template <typename TARGET_TYPE> constexpr static f_sum make() noexcept
{
return f_sum{&invoke<TARGET_TYPE>};
}
void operator()(void * i_dest, void const * i_first, void const * i_second) const noexcept
{
(*m_function)(i_dest, i_first, i_second);
}
void (*m_function)(void * i_dest, void const * i_first, void const * i_second);
template <typename TARGET_TYPE>
static void invoke(void * i_dest, void const * i_first, void const * i_second)
{
auto const & first = *static_cast<TARGET_TYPE const *>(i_first);
auto const & second = *static_cast<TARGET_TYPE const *>(i_second);
new (i_dest) TARGET_TYPE(first + second);
}
};
template <typename... FEATURES>
any<FEATURES...> operator+(const any<FEATURES...> & i_first, const any<FEATURES...> & i_second)
{
using namespace density;
static_assert( // for simplicity we don't SFINAE
"The provided any leaks the feature sum_impl");
if (i_first.type() != i_second.type() || i_first.type() == typeid(void))
throw std::runtime_error("Mismatching types");
return any<FEATURES...>(i_first.get_runtime_type(), [&](void * i_dest) {
i_first.template get_type_feature<f_sum>()(
i_dest, i_first.object_ptr(), i_second.object_ptr());
});
}

Member Typedef Documentation

using feature_list_type = typename std::conditional< (sizeof...(FEATURES) > 0), feature_list<FEATURES...>, default_type_features>::type

feature_list associated to the template arguments. If to template arguments is provided, default_type_features is used.

using tuple_type = typename feature_list_type::tuple_type

Alias for feature_list_type::tuple_type, a specialization of std::tuple that contains all the features.

using RuntimeRype = runtime_type<f_size, feature_list<f_none, f_alignment> >;
static_assert( std::is_same<
RuntimeRype::tuple_type,
std::tuple<f_size, f_alignment>
>::value, "");

Implementation note: runtime_type is actually a wrapper around a pointer to a static constexpr instance of this tuple.

Constructor & Destructor Documentation

constexpr runtime_type ( )
defaultnoexcept

Constructs an empty runtime_type not associated with any type. Trying to use any feature of an empty runtime_type leads to undefined behavior.

Postcoditions: Given a specialization of runtime_type R and type T, the following conditions hold:

constexpr R r;
static_assert(r.empty(), "");
static_assert(!r.is<T>(), "");

Throws: nothing

constexpr runtime_type ( const runtime_type< OTHER_FEATURES... > &  i_source)
inlinenoexcept

Generalized copy constructor. This constructor does not participate in overload resolution unless runtime_type::tuple and runtime_type<OTHER_FEATURES...>::tuple are the same (that is the feature lists of the two runtime_type are equivalent).

Throws: nothing

using Rt1 = runtime_type<f_size, f_alignment>;
using Rt2 = runtime_type<feature_list<f_size>, f_none, f_alignment>;
Rt1 t1 = Rt1::make<int>();
Rt1 t2 = t1; // valid because Rt1 and Rt2 are equivalent
assert(t1 == t2);
using Rt3 =
runtime_type<feature_list<f_size, f_default_construct>, f_none, f_alignment>;
// Rt3 includes f_default_construct, so it's not equivalent to Rt1 and Rt2
static_assert(!std::is_constructible<Rt1, Rt3>::value, "");

Member Function Documentation

static constexpr runtime_type make ( )
inlinestaticnoexcept

Creates a runtime_type bound to a target type.

Template Parameters
TARGET_TYPEtype to bind to the returned runtime_type.

Postcoditions: Given a specialization of runtime_type R and type T, the following conditions hold:

constexpr auto r = R::make<T>();
static_assert(!r.empty(), "");
static_assert(r != R(), "");
static_assert(r.is<T>(), "");

Throws: nothing

DENSITY_CPP14_CONSTEXPR runtime_type& operator= ( const runtime_type< OTHER_FEATURES... > &  i_source)
inlinenoexcept

Generalized copy assignment. This function does not participate in overload resolution unless runtime_type::tuple and runtime_type<OTHER_FEATURES...>::tuple are the same (that is the feature lists of the two runtime_type are equivalent).

If the compiler conforms to C++14 (in particular __cpp_constexpr >= 201304) this function is constexpr.

Throws: nothing

using Rt1 = runtime_type<f_size, f_alignment>;
using Rt2 = runtime_type<feature_list<f_size>, f_none, f_alignment>;
Rt1 t1 = Rt1::make<int>();
Rt1 t2;
t2 = t1; // valid because Rt1 and Rt2 are equivalent
assert(t1 == t2);
using Rt3 =
runtime_type<feature_list<f_size, f_default_construct>, f_none, f_alignment>;
// Rt3 includes f_default_construct, so it's not equivalent to Rt1 and Rt2
static_assert(!std::is_assignable<Rt1, Rt3>::value, "");
constexpr bool empty ( ) const
inlinenoexcept

Returns whether this runtime_type is not bound to a target type.

Throws: nothing

DENSITY_CPP14_CONSTEXPR void clear ( )
inlinenoexcept

Unbinds from a target. If the runtime_type was already empty this function has no effect.

If the compiler conforms to C++14 (in particular __cpp_constexpr >= 201304) this function is constexpr.

Throws: nothing

constexpr size_t size ( ) const
inlinenoexcept

Invokes the feature f_size and returns the size of the target type. Equivalent to get_feature<f_size>()().

The effect of this function is the same of this code:

return sizeof(TARGET_TYPE);

Requires:

  • If the feature f_size is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

Postcoditions:

  • The return value is above zero.

Throws: nothing

constexpr size_t alignment ( ) const
inlinenoexcept

Invokes the feature f_alignment and returns the alignment of the target type. Equivalent to get_feature<f_alignment>()().

The effect of this function is the same of this code:

return alignof(TARGET_TYPE);

Requires:

  • If the feature f_alignment is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

Postcoditions:

  • The return value is above zero.

Throws: nothing

void default_construct ( void *  i_dest) const
inline

Invokes the feature f_default_construct to value-initialize an instance of the target type. Equivalent to get_feature<f_default_construct>()(i_dest).

Parameters
i_destpointer to the destination buffer in which the target type is in-place constructed.

The effect of this function is the same of this code:

new(i_dest) TARGET_TYPE();

Note that if TARGET_TYPE is not a class type, it is zero-initialized.

Requires:

  • If the feature f_default_construct is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

Postcoditions:

  • The destination buffer contains an instance of the TARGET_TYPE.

Throws: anything that the constructor of the target type throws.

void copy_construct ( void *  i_dest,
const void *  i_source 
) const
inline

Invokes the feature f_copy_construct to copy-construct an instance of the target type. Equivalent to get_feature<f_copy_construct>()(i_dest, i_source).

Parameters
i_destpointer to the destination buffer in which the target type is in-place constructed.
i_sourcepointer to an instance of the target type to be used as source for the copy.

The effect of this function is the same of this code:

new(i_dest) TARGET_TYPE( *static_cast<const TARGET_TYPE*>(i_source) );

Requires:

  • If the feature f_copy_construct is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

  • The runtime_type is not empty
  • The destination pointer and the source pointer are not null
  • The destination buffer is large at least as the result of runtime_type::size and it's aligned at least according to runtime_type::alignment
  • The source pointer points to an object whose dynamic type is the target type

Postcoditions:

  • The destination buffer contains an instance of the TARGET_TYPE.

Throws: anything that the copy constructor of the target type throws.

void move_construct ( void *  i_dest,
void *  i_source 
) const
inline

Invokes the feature f_move_construct to move-construct an instance of the target type. Equivalent to get_feature<f_move_construct>()(i_dest, i_source).

Parameters
i_destpointer to the destination buffer in which the target type is in-place constructed.
i_sourcepointer to an instance of the target type to be used as source for the move

The effect of this function is the same of this code:

new(i_dest) TARGET_TYPE( std::move(*static_cast<TARGET_TYPE*>(i_source)) );

Requires:

  • If the feature f_move_construct is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

  • The runtime_type is not empty
  • The destination pointer and the source pointer are not null
  • The destination buffer is large at least as the result of runtime_type::size and it's aligned at least according to runtime_type::alignment
  • The source pointer points to an object whose dynamic type is the target type

Postcoditions:

  • The destination buffer contains an instance of the TARGET_TYPE.
  • The source buffer contains a moved-from instance of the target type.

Throws: anything that the move constructor of the target type throws.

void destroy ( void *  i_dest) const
inlinenoexcept

Invokes the feature f_destroy to destroy an instance of the target type. Equivalent to get_feature<f_destroy>()(i_dest).

Parameters
i_destpointer to an instance of the target type to be destroyed.

The effect of this function is the same of this code:

static_cast<TARGET_TYPE*>(i_source)->~TARGET_TYPE::TARGET_TYPE();

Requires:

  • If the feature f_destroy is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

  • The runtime_type is not empty
  • The destination pointer is not null and it points to an object whose dynamic type is the target type

Postcoditions:

  • The destination buffer does not contain an instance of the TARGET_TYPE.

Throws: nothing.

const std::type_info& type_info ( ) const
inlinenoexcept

Invokes the feature f_rtti to return the std::type_info of the target type. Equivalent to get_feature<f_rtti>()().

The effect of this function is the same of this code:

return typeid(TARGET_TYPE);

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

Requires:

  • If the feature f_rtti is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Throws: nothing.

bool are_equal ( const void *  i_first,
const void *  i_second 
) const
inlinenoexcept

Invokes the feature f_equal to compare two instances of the target type. Equivalent to get_feature<f_equal>()(i_first, i_second).

Parameters
i_firstpointer to an instance of the target type
i_secondpointer to an instance of the target type
Returns
true if the two objects compare equal, false otherwise

The effect of this function is the same of this code:

return *static_cast<const TARGET_TYPE*>(i_first) == *static_cast<const TARGET_TYPE*>(i_second);

Requires:

  • If the feature f_equal is not included in the FEATURE_LIST a compile error is reported (this function is not SFINAE-friendly).

Precoditions: The behavior is undefined if any of these conditions is not satisfied:

  • The runtime_type is not empty
  • The first pointer is not null and it points to an object whose dynamic type is the target type
  • The second pointer is not null and it points to an object whose dynamic type is the target type

Throws: nothing.

constexpr bool operator== ( const runtime_type< FEATURES > &  i_other) const
inlinenoexcept

Returns true whether this two runtime_type have the same target type. All empty runtime_type's compare equal.

Throws: nothing.

constexpr bool operator!= ( const runtime_type< FEATURES > &  i_other) const
inlinenoexcept

Returns true whether this two runtime_type have different target types. All empty runtime_type's compare equal.

Throws: nothing.

constexpr bool is ( ) const
inlinenoexcept

Returns whether the target type of this runtime_type is exactly the one specified in the template parameter. Equivalent to *this == runtime_type::make<TARGET_TYPE>()<&code>

Throws: nothing.

auto r = runtime_type<>::make<int>();
assert(r.is<int>());
assert(!r.is<double>());

Friends And Related Function Documentation

void swap ( runtime_type< FEATURES > &  i_first,
runtime_type< FEATURES > &  i_second 
)
friend

Swaps two instances.

Throws: nothing

auto r1 = runtime_type<>::make<int>();
auto r2 = runtime_type<>::make<double>();
swap(r1, r2);
assert(r1.is<double>());
assert(r2.is<int>());

The documentation for this class was generated from the following file: