keebus
keebus

Reputation: 1010

C++ index of type during variadic template expansion

I have a simple yet daunting problem I can't solve by myself. I have something like

template<class T, class... Args>
T* create(SomeCastableType* args, size_t numArgs)
{
  return new T(static_cast<Args>(args[INDEX_OF_EXPANSION])...);
}

Suppose SomeCastableType is castable to any type. Obviously what I can't get is that INDEX_OF_EXPANSION.

Thank you very much for your help.

Upvotes: 23

Views: 12370

Answers (5)

Alex
Alex

Reputation: 21

Here's a slight variation with fold expressions (no recursion needed):

        template <typename Target, size_t Idx, typename... List>
        constexpr size_t type_index() {
            size_t idx   = 0;
            size_t count = Idx;
            bool   found = false;
    //This is effectively an unrolled for loop
    //we're checking each T in the parameter pack one by one
    //as we go we update whether we've found the right index
    //we assume multiple matching types may be in a parameter list 
    //so we need Idx to distinguish which one we're after
    //if we enter a type that's not in our parameter pack we'll
    //return sizeof...(List) like if we're using std::find()
            ((
                 found = found || (std::is_same<Target, List>::value && count == 0),
                 idx   = idx +
                           (!std::is_same<Target, List>::value && !found) ||
                       (std::is_same<Target, List>::value && count > 0),
                 count = count - (std::is_same<Target, List>::value && count > 0)),
                ...);
            return idx;
        }
//example:
type_index<int, 0, double, int, double, int>() == 1
type_index<int, 1, double, int, double, int>() == 3

Upvotes: 1

wheybags
wheybags

Reputation: 657

With c++17's constexpr if, we can get a much more readable / intelligible implementation of an index-lookup function (I never managed to get my head around the other answers here):

template<typename Target, typename ListHead, typename... ListTails>
constexpr size_t getTypeIndexInTemplateList()
{
    if constexpr (std::is_same<Target, ListHead>::value)
        return 0;
    else
        return 1 + getTypeIndexInTemplateList<Target, ListTails...>();
}

This can be used as follows:

size_t index = getTypeIndexInTemplateList<X,  Foo,Bar,X,Baz>(); // this will return 2

Or if you have a variadically templated type and want to get an index in it:

template<typename... Types>
class Container
{
public:
    size_t getIndexOfType<typename T>() {  return getTypeIndexInTemplateList<T, Types...>(); }
};

...

Container<Foo, Bar, X, Baz> container;
size_t container.getIndexOfType<X>(); // will return 2

The way it works is by recursively eliminating types from the list. So the call order for the first example is basically:

getTypeIndexInTemplateList<X,  Foo,  Bar,X,Baz>() // ListHead = Foo, ListTails = Bar,X,Baz
getTypeIndexInTemplateList<X,  Bar,  X,Baz>()     // ListHead = Bar, ListTails = X, Baz
getTypeIndexInTemplateList<X,  X,    Baz>()       // ListHead = X, so now we return. Recursive addition takes care of calculating the correct index

The function is constexpr, so this will all get executed at compile time, it will just be a constant at runtime.

If you ask for a type that is not present in the list, it will generate a compile error, as it will try to call the function with too few template arguments. And of course, this will just return the index of the first instance of the type in the list, if the type is present more than once.

Upvotes: 5

Dean Seo
Dean Seo

Reputation: 5683

Suppose SomeCastableType is castable to any type. Obviously what I can't get is that INDEX_OF_EXPANSION.

Since C++14, you can do the indices trick @Xeo mentioned with the support from the standard library, by using the std::make_index_sequence helper, as follows:

template<class T, class... Args, std::size_t... Is>
T* create(SomeCastableType* p, std::index_sequence<Is...>)
{
    return new T(static_cast<Args>(p[Is])...);
}

template<class T, class... Args>
T* create(SomeCastableType* p, std::size_t num_args)
{
    return create<T, Args...>(p, std::make_index_sequence<sizeof...(Args)>());
}

Upvotes: 4

Xeo
Xeo

Reputation: 131799

Indices trick, yay~

template<class T, class... Args, std::size_t... Is>
T* create(U* p, indices<Is...>){
  return new T(static_cast<Args>(p[Is])...);
}

template<class T, class... Args>
T* create(U* p, std::size_t num_args){
  assert(num_args == sizeof...(Args));
  return create<T, Args...>(p, build_indices<sizeof...(Args)>{});
}

Of course, I strongly advise using a smart pointer and a std::vector instead of raw pointers.

Upvotes: 23

Kerrek SB
Kerrek SB

Reputation: 477100

You need a helper:

#include <tuple>

template <typename T, bool, typename Tuple, unsigned int ...I>
struct helper
{
    static T * go(S * args)
    {
        return helper<T, sizeof...(I) + 1 == std::tuple_size<Tuple>::value,
                      Tuple, I..., sizeof...(I)>::go(args);
    }
};

template <typename T, typename ...Args, unsigned int ...I>
struct helper<T, true, std::tuple<Args...>, I...>
{
    static T * go(S * args)
    {
        return new T(static_cast<Args>(args[I])...);
    }
};

template <typename T, typename ...Args>
T * create(S * args)
{
    return helper<T, sizeof...(Args) == 0, std::tuple<Args...>>::go(args);
}

Edit: Tested, seems to work.

Upvotes: 3

Related Questions