Skip to content

Instantly share code, notes, and snippets.

@EricWF
Last active August 29, 2015 13:57
Show Gist options
  • Save EricWF/9540803 to your computer and use it in GitHub Desktop.
Save EricWF/9540803 to your computer and use it in GitHub Desktop.
#ifndef ENTITY_HPP
#define ENTITY_HPP
# include <elib/aux.hpp>
# include <elib/any.hpp>
# include <unordered_map>
# include <typeinfo>
# include <typeindex>
namespace example
{
using namespace elib;
// fwd
class entity;
// all tags have to inherit from this for safety
// prevents things like int or std::string from being used as tags
struct method_base {};
namespace detail
{
////////////////////////////////////////////////////////////////////////
//
template <class T>
struct deduce_result
{
static_assert(
elib::aux::never<T>::value
, "cannot deduce result for non-function type"
);
};
template <class Ret, class ...Args>
struct deduce_result<Ret(Args...)>
{
using type = Ret;
};
////////////////////////////////////////////////////////////////////////
//
template <class T>
struct method_traits
{
static_assert(
aux::never<T>::value
, "cannot check method on non function type"
);
};
template <class Tag, class ...Args>
struct method_traits<Tag(Args...)>
{
// the type used to call the function
using type = Tag(Args...);
// The tag containing meta-information
using tag = Tag;
// the actual type (without pointer or ref) of the actual method pointer
using method_type = typename Tag::type;
// the return type of the method
using result_type = typename deduce_result<method_type>::type;
// the signature of the method externally
using signature = result_type(Args...);
// true if we require a "this" entity to be passed as the first param
static constexpr bool requires_this =
elib::or_<
aux::is_same<method_type, result_type(entity &, Args...)>
, aux::is_same<method_type, result_type(entity const &, Args...)>
>::value;
// true if the method is a "const" method
static constexpr bool is_const =
elib::or_<
aux::is_same<method_type, result_type(Args...)>
, aux::is_same<method_type, result_type(entity const &, Args...)>
>::value;
// if the call does not require this, this_type = void.
// otherwise it is entity & if non-const and entity const & if const
using this_type =
elib::if_c_t<
requires_this
, elib::if_c_t<is_const, entity const &, entity &>
, void
>;
// this may seem dumb, but in order to run the static_assert's
// we need to reach in and touch something. this is what we touch
static constexpr bool good = true;
private:
static_assert(
aux::is_base_of<method_base, Tag>::value
, "Tag must derive from method base"
);
// the signature and the method type can either have the sames arguments
// or the method_type can require a reference to "this" entity.
static_assert(
aux::is_same<method_type, signature>::value
|| aux::is_same<method_type, result_type(entity const &, Args...)>::value
|| aux::is_same<method_type, result_type(entity &, Args...)>::value
, "Invalid call signature for method"
);
};
template <class Tag, class ...Args>
constexpr bool method_traits<Tag(Args...)>::requires_this;
template <class Tag, class ...Args>
constexpr bool method_traits<Tag(Args...)>::is_const;
template <class Tag, class ...Args>
constexpr bool method_traits<Tag(Args...)>::good;
template <class TaggedMethod, class ...InvokeArgs>
struct check_call : elib::false_ {};
template <class Tag, class ...Args>
struct check_call<Tag(Args...), Args...> : elib::true_ {};
template <
class TaggedMethod
, bool = method_traits<TaggedMethod>::requires_this
> struct method_adapter;
template <class Tag, class ...Args>
struct method_adapter<Tag(Args...), true>
{
using traits = method_traits<Tag(Args...)>;
static_assert(traits::good, "Bad method traits");
using method_type = typename traits::method_type;
using result_type = typename traits::result_type;
using entity_type =
elib::if_c_t<
traits::is_const
, const entity
, entity
>;
public:
method_adapter(entity_type & e, method_type* mptr)
: m_entity(&e), m_method_ptr(mptr)
{}
template < ELIB_ENABLE_IF(traits::requires_this) >
result_type operator()(Args &&... args)
{
return (*m_method_ptr)(*m_entity, elib::forward<Args>(args)...);
}
private:
entity_type *m_entity;
method_type *m_method_ptr;
};
template <class Tag, class ...Args>
struct method_adapter<Tag(Args...), false>
{
using traits = method_traits<Tag(Args...)>;
static_assert(traits::good, "Bad method traits");
using method_type = typename traits::method_type;
using result_type = typename traits::result_type;
using entity_type =
elib::if_c_t<
traits::is_const
, const entity
, entity
>;
public:
explicit method_adapter(method_type *mptr)
: m_method_ptr(mptr)
{}
method_adapter(entity_type &, method_type* mptr)
: m_method_ptr(mptr)
{}
result_type operator()(Args &&... args)
{
return (*m_method_ptr)(elib::forward<Args>(args)...);
}
private:
method_type *m_method_ptr;
};
} // namespace detail
// this is a sample implementation of an entity that accepts methods
// WARNING: the type index used as the key is different that the type it holds
class entity
{
public:
template <
class TaggedMethod
, ELIB_ENABLE_IF(detail::method_traits<TaggedMethod>::good)
>
bool has_method()
{
return m_methods.count(std::type_index(typeid(TaggedMethod)));
}
template<
class TaggedMethod
, ELIB_ENABLE_IF(detail::method_traits<TaggedMethod>::good)
>
void set_method(typename detail::method_traits<TaggedMethod>::method_type* method_ptr)
{
using method_ptr_type = typename detail::method_traits<TaggedMethod>::method_type*;
m_methods[std::type_index(typeid(TaggedMethod))] = elib::any(method_ptr);
}
template <
class TaggedMethod
, ELIB_ENABLE_IF(detail::method_traits<TaggedMethod>::good)
>
void remove_method()
{
m_methods.erase(std::type_index(typeid(TaggedMethod)));
}
template <
class TaggedMethod
, class ...Args
, ELIB_ENABLE_IF(detail::method_traits<TaggedMethod>::is_const == false)
, ELIB_ENABLE_IF(detail::check_call<TaggedMethod, Args...>::value)
>
typename detail::method_traits<TaggedMethod>::result_type
call(Args &&... args)
{
using traits = detail::method_traits<TaggedMethod>;
auto pos = m_methods.find(std::type_index(typeid(TaggedMethod)));
if (pos == m_methods.end())
throw "TODO: No such method";
auto fn_ptr = elib::any_cast<typename traits::method_type*>(pos->second);
detail::method_adapter<TaggedMethod> adapter(*this, fn_ptr);
return adapter(elib::forward<Args>(args)...);
}
template <
class TaggedMethod
, class ...Args
, ELIB_ENABLE_IF(detail::method_traits<TaggedMethod>::is_const)
, ELIB_ENABLE_IF(detail::check_call<TaggedMethod, Args...>::value)
>
typename detail::method_traits<TaggedMethod>::result_type
call(Args &&... args) const
{
using traits = detail::method_traits<TaggedMethod>;
auto pos = m_methods.find(std::type_index(typeid(TaggedMethod)));
if (pos == m_methods.end())
throw "TODO: no such method";
auto fn_ptr = elib::any_cast<typename traits::method_type*>(pos->second);
detail::method_adapter<TaggedMethod> adapter(*this, fn_ptr);
return adapter(elib::forward<Args>(args)...);
}
private:
std::unordered_map<std::type_index, elib::any> m_methods;
};
}
#endif
#include "entity.hpp"
#include <iostream>
namespace example
{
// this is a tag
struct move_ : method_base
{
using type = void(entity const &, int x, int y);
};
void run()
{
entity e;
std::cout << e.has_method<move_(int, int)>() << std::endl;
auto move_fn = [](entity &, int, int){ std::cout << "Here!" << std::endl; };
e.set_method<move_(int, int)>(move_fn);
std::cout << e.has_method<move_(int, int)>() << std::endl;
e.call<move_(int, int)>(1, 2);
e.remove_method<move_(int, int)>();
std::cout << e.has_method<move_(int, int)>() << std::endl;
}
}
int main()
{
std::cout << std::boolalpha;
example::run();
}
// example one, using tag dispatching.
// in this example you need to define one tag for every type of function you want access to.
// NOTE: it would be pretty easy to generate this with a macro.
constexpr struct ActiveTextureARB_tag_t
{
using type = GL_ActiveTextureARB_Func;
static const char* name = "glActiveTextureARB";
} ActiveTextureARB_tag;
const char *ActiveTextureARB_tag_t::name;
template <class Tag, class ...Args>
typename std::result_of<typename Tag::type(std::declval<Args>()...)>::type
call_ogl(Tag, Args &&... args)
{
using fn_type = typename Tag::type;
fn_type fn = (fn_type) SDL_GL_GetProcAddress(Tag::name);
return (*fn)(std::forward<Args>(args)...);
}
sample usage:
auto ret = call_ogl(ActiveTextureARB_tag);
other possible signatures:
auto ret = call_ogl<ActiveTextureARB_tag>(params...);
// NOTE: this version does not need any struct tag definitions
auto ret = call_ogl<ActiveTextureARB_Func>(String name, params...);
// A definition would look like
// Helper function to deduce return type
template <class T>
struct detect_return_type;
template <class Ret,class ...Args>
struct detect_return_type<Ret(Args...)>
{
using type = Ret;
};
template <class Ret,class ...Args>
struct detect_return_type<Ret(*)(Args...)>
{
using type = Ret;
};
template <class FunctionType, class ...Args>
typename detect_return_type<FunctionType>::type
call_ogl(const char *name, Args &&...args)
{
FunctionType fn = (FunctionType) SDL_GetProcAddr(name);
return fn(std::forward<Args>(args)...);
}
#include "entity.hpp"
#include <iostream>
namespace example
{
// this is a tag
struct move_ : method_base
{
using type = void(entity const &, int x, int y);
};
void run()
{
entity e;
e.has_method<move_>();
e.set_method<move_>([](){});
e.call<move_>(0, 0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment