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

3k views Asked by At

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?

4

There are 4 answers

5
Barry On BEST ANSWER

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.

0
kirbyfan64sos On

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...>();
}
0
Yakk - Adam Nevraumont On

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.

2
RabtFt On

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...>();
}