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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With