You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

180 lines
6.1 KiB

/**
* Copyright (c) 2018 Niklas Rosenstein
* MIT licensed.
*
* @description An alternative to C++ RTTI and `dynamic_cast<>()`.
*/
#pragma once
#include <type_traits>
#include "macros.hpp"
namespace niklasrosenstein {
namespace detail {
/**
* Helper structure to read the ID of a type from its `type_id` member
* using SFINAE. Thanks to T.C. on StackOverflow for this workaround.
* See http://stackoverflow.com/q/40321597/791713.
*/
struct type_id_impl {
template <class T>
static constexpr auto get(...) -> int { return 0; }
template <class T>
static constexpr auto get(int) -> decltype(T::type_id) { return T::type_id; }
};
/**
* Template that yields the ID of a type either from its `T::type_id`
* member or by a specialization.
*/
template <class T> struct type_id {
static int const value = type_id_impl::get<T>(0);
};
/**
* Implementation of the actual type-casting functionality. Wrapped in
* a struct to be able to mark all member methods friends of classes
* that implement the `NR_TYPEID()` macro.
*/
struct type_cast_impl {
/**
* A trait to enable a type #R only if type #E is defined. Used in the
* return type of a function to enable/disable the function respectively.
* Example:
*
* template <typename T>
* auto func(T& obj) -> typename enable_by<decltype(T::member), int>::type {
* return obj.member();
* }
*/
template <class E, class R> struct enable_by {
using type = R;
};
/**
* Plain check if the requested #target_id matches the current class
* of the #ptr. Returns `nullptr` if they do not match.
*/
template <class C>
static void* cast(C const* ptr, int target_id) {
if (type_id<C>::value == target_id) {
return static_cast<void*>(const_cast<C*>(ptr));
}
return nullptr;
}
/**
* Used if the current base-class #B supports the `__nonvirtual_cast()`
* member function (see #NR_TYPE_ID() macro).
*/
template <class C, class B, class... Bases>
static auto cast(C const* ptr, int target_id)
-> typename enable_by<decltype(B::__nonvirtual_cast), void*>::type
{
void* result = static_cast<B const*>(ptr)->__nonvirtual_cast(target_id);
if (result) return result;
return cast<C, Bases...>(ptr, target_id);
}
/**
* Used when the current base-class #B does not support the
* `__nonvirtual_cast()` member function (for types associated with
* #NR_TYPE_IDEX()).
*/
template <class C, class B, class... Bases>
static void* cast(C const* ptr, int target_id) {
void* result = cast<B>(ptr, target_id);
if (result) return result;
return cast<C, Bases...>(ptr, target_id);
}
};
} // namespace detail
/**
* Returns the ID of a type that implements the `NR_TYPEID()` macro or
* for which there exists a specialization with `NR_TYPEID_EX()`.
*/
template <class T> inline constexpr int type_id() {
return detail::type_id<T>::value;
}
/**
* This macro makes a class compatible to the type casting system provided
* with this lirbary. It assigns a `type_id` member and creates two functions
* named `__virtual_cast()` and `__nonvirtual_cast()` as well as some required
* friend declarations to allow the macro be placed in the private section.
*/
#define NR_TYPEID(cls, id, ...) \
template <class _TYPEID_R, class _TYPEID_T> friend _TYPEID_R const* niklasrosenstein::type_cast(_TYPEID_T const*); \
template <class _TYPEID_R, class _TYPEID_T> friend _TYPEID_R* niklasrosenstein::type_cast(_TYPEID_T*); \
friend struct niklasrosenstein::detail::type_id_impl; \
friend struct niklasrosenstein::detail::type_cast_impl; \
static const int type_id = (id); \
void* __nonvirtual_cast(int target_id) const { \
return niklasrosenstein::detail::type_cast_impl::cast<cls, ##__VA_ARGS__>(this, target_id); \
} \
NR_INLINE_PRAGMA_CLANG(clang diagnostic push); \
NR_INLINE_PRAGMA_CLANG(clang diagnostic ignored "-Wunknown-pragmas"); \
NR_INLINE_PRAGMA_CLANG(clang diagnostic ignored "-Winconsistent-missing-override"); \
virtual void* __virtual_cast(int target_id) const { \
return this->__nonvirtual_cast(target_id); \
} \
NR_INLINE_PRAGMA_CLANG(clang diagnostic pop);
/**
* Associate an external class with a type ID. This allows you to assign
* a type ID to a type that does not provide a `type_id` member.
*/
#define NR_TYPEID_EX(type, id) \
namespace niklasrosenstein { \
namespace detail { \
template <> struct type_id<type> { \
static int const value = (id); \
}; \
} \
}
/**
* Dynamically cast an object to another type. If the object does not
* support that type, `nullptr` is returned instead. Note that at least
* the type #T must have the #NR_TYPE_ID() macro in its class definition.
* @{
*/
template <class R, class T>
R const* type_cast(T const* ptr) {
return reinterpret_cast<R const*>(ptr->__virtual_cast(type_id<R>()));
}
template <class R, class T>
R* type_cast(T* ptr) {
return reinterpret_cast<R*>(ptr->__virtual_cast(type_id<R>()));
}
// @}
/**
* Adapter and automatic caster for objects that support a specific
* interface. Represents a pointer to that object. If the constructor
* can not cast the object to the type #T, the #type_adapter object
* will be filled with a nullptr.
*/
template <class T>
class type_adapter {
T* ptr;
public:
type_adapter(T* ptr_=nullptr) : ptr(ptr_) {}
type_adapter(type_adapter const& other) : ptr(other.ptr) {}
type_adapter(type_adapter&& other) : ptr(other.ptr) { other.ptr = nullptr; }
type_adapter& operator = (T* ptr_) { this->ptr = ptr; return *this; }
template <typename R>
type_adapter(R* ptr_) : ptr(type_cast<T>(ptr)) {}
operator T* () const { return this->ptr; }
T* operator -> () const { return this->ptr; }
};
} // namespace niklasrosenstein