Reputation: 108
I have a variadic Engine template class:
template <typename ... Components> class Engine;
I'd like to assign a number to each component at compile time which is equivalent to their ordering. This would be returned when making the following call:
template <typename Component> int ordinal();
So for example if:
Engine<PositionComponent, PhysicsComponent, InputComponent> engine;
was declared, the call:
engine.ordinal<PhysicsComponent>();
would return 1 and a similar call with InputComponent instead of PhysicsComponent would return 2.
Is it possible, and if yes, how would one go about it?
Upvotes: 5
Views: 3134
Reputation: 303117
In Boost.Mp11, this is a short one-liner (as always):
template <typename... Components>
struct Engine {
template <typename Component>
static constexpr int ordinal() {
return mp_find<Engine, Component>::value;
}
};
Note that if Component
is absent, this will return sizeof...(Components)
. If desired, you can add a static assertion to verify this.
My original answer follows below the fold...
So you want to find the index of 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>
{ };
Usage:
template <typename Component>
size_t ordinal() { return index<Component, Components...>::value; }
As constructed, trying to get the ordinal
of a Component
not in Components...
will be a compile error. Which seems appropriate.
Upvotes: 11
Reputation: 153
I'm adding this for completeness sake, it utilizes C++11's constexpr functionality, and a few stl functions. I feel it is a little cleaner than the 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...>();
}
Upvotes: 1
Reputation: 275500
My goal below is to keep things in the compile-time realm as much as possible.
This is an alias to remove some boilerplate. std::integral_constant
is a wonderful std
type that stores a compile-time determined integer-type:
template<std::size_t I>
using size=std::integral_constant<std::size_t, I>;
Next, a index_of
type, and an index_of_t
that is slightly 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 a pure std::integral_constant
, instead of a type inheriting from 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{};}
it is not only constexpr
, it returns a value that encodes its value in its type. size<?>
has a constexpr operator size_t()
as well as an operator()
, so you can use it in most spots that expect integer types seamlessly.
You could also use:
template<class Component>
using ordinal = index_of_t<Component, Components...>;
and now ordinal<Component>
is a type representing the index of the component, instead of a function.
Upvotes: 2
Reputation: 10727
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 have used structs, but I find this much cleaner (without all the ::type
ugliness).
If you want 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...>();
}
Upvotes: 0