How can I get the index of a type in a variation class template?
I have a Variadic Engine template class:
template <typename ... Components> class Engine;
I would like to assign a number to each component at compile time, which is equivalent to their ordering. This will be returned when the next call is made:
template <typename Component> int ordinal();
So, for example, if:
Engine<PositionComponent, PhysicsComponent, InputComponent> engine;
call:
engine.ordinal<PhysicsComponent>();
will return 1, and a similar call with InputComponent instead of PhysicalComponent will return 2.
Is it possible, and if so, how would it be done?
source to share
So you want to find the index Component
in Components...
?
template <typename... >
struct index;
// found it
template <typename T, typename... R>
struct index<T, T, R...>
: std::integral_constant<size_t, 0>
{ };
// still looking
template <typename T, typename F, typename... R>
struct index<T, F, R...>
: std::integral_constant<size_t, 1 + index<T,R...>::value>
{ };
Using:
template <typename Component>
size_t ordinal() { return index<Component, Components...>::value; }
As built, trying to get ordinal
for Component
not in Components...
is a compilation error. Seems appropriate.
source to share
My goal is to keep things compile time as much as possible.
This is an alias for removing some templates. std::integral_constant
is a wonderful type std
that stores a specific compile-time integer type:
template<std::size_t I>
using size=std::integral_constant<std::size_t, I>;
Further, the type index_of
and index_of_t
, which is somewhat easier to use:
template<class...>struct types{using type=types;};
template<class T, class Types>struct index_of{};
template<class T, class...Ts>
struct index_of<T, types<T, Ts...>>:size<0>{};
template<class T, class T0, class...Ts>
struct index_of<T, types<T0, Ts...>>:size<
index_of<T,types<Ts...>>::value +1
>{};
This alias returns pure std::integral_constant
instead of a type that inherits it:
template<class T, class...Ts>
using index_of_t = size< index_of<T, types<Ts...>>::value >;
Finally, our function:
template <class Component>
static constexpr index_of_t<Component, Components...>
ordinal() const {return{};}
this not only constexpr
but also returns a value that encodes its value in its type. size<?>
has constexpr operator size_t()
as well operator()
, so you can use it at most points that integer types are expected.
You can also use:
template<class Component>
using ordinal = index_of_t<Component, Components...>;
and now it ordinal<Component>
is a type representing the index of the component, not a function.
source to share
UNTESTED:
template <int, typename>
constexpr int index_of() { return -1; } // type not found
template <int N, typename Component, typename Cur, typename... Components>
constexpr int index_of() {
return std::is_same<Component, Cur>::value ? N : index_of<N+1, Component, Components...>();
}
template <typename... Components>
template <typename Component>
constexpr int engine<Components...>::ordinal() {
return index_of<0, Component, Components...>();
}
I could use structs, but I find it much cleaner (without all the ::type
ugliness).
If you need a compile-time error when the type is not found, change ordinal
to:
template <typename... Components>
template <typename Component>
constexpr int engine<Components...>::ordinal() {
static_assert(index_of<0, Component, Components...>()!=-1, "invalid component");
return index_of<0, Component, Components...>();
}
source to share
I'm adding this for completeness, it uses the C ++ 11 constexpr functionality and a few stl functions. I feel like this is a little cleaner than other solutions.
//Same type
template <typename Target,typename T,typename ...Rest>
constexpr typename std::enable_if<std::is_same<Target,T>::value, size_t>
_ordinal(){
return 0;
}
//Different types
template <typename Target,typename T,typename ...Rest>
constexpr typename std::enable_if<!std::is_same<Target,T>::value, size_t>
_ordinal(){
return 1+_ordinal<Target,Rest...>();
}
source to share