Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the index of a type in a variadic class template?

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?

like image 485
Natalie Cuthbert Avatar asked Jun 09 '15 15:06

Natalie Cuthbert


2 Answers

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.

like image 180
Barry Avatar answered Oct 08 '22 23:10

Barry


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.

like image 22
Yakk - Adam Nevraumont Avatar answered Oct 08 '22 23:10

Yakk - Adam Nevraumont