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.