Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template class argument containers instantiation

I want to instantiate a variadic template class Store<TArgs...> that has a std::vector for each type in the TArgs... pack.

template<typename... TArgs> class Store {
    // obviously not valid code
    // assuming each type of TArgs... has a `unsigned int` id that can be
    // retrieved with getId<T>()
    std::array<sizeof...(TArgs), std::vector<TArgs...>> bags;

    template<typename T> void add(T mValue) { 
        bags[getId<T>()].push_back(mValue); 
    }

    template<typename T> std::vector<T>& get() { 
        return bags[getId<T>()]; 
    }
};

Let's say I have a Store<int, float, double>. I obviously know, at compile time, that it will be able to store int, float and double values.

I could use template specializations:

template<> class Store<int, float, double> {
    std::vector<int> vi;
    std::vector<float> vf;
    std::vector<double> vd;

    template<typename T> void add(T);
    template<> void add<int>(int mValue) { vi.push_back(mValue); }
    template<> void add<float>(float mValue) { vf.push_back(mValue); }
    template<> void add<double>(double mValue) { vd.push_back(mValue); }
    // ...
};

...but that would require hand-writing every possible combination of types, and would not work with user-defined types.

I'm confident the compiler knows everything that's required to generate a class like Store<int, float, double> using variadic templates - is there a way to actually express this intent?

like image 830
Vittorio Romeo Avatar asked Oct 19 '13 07:10

Vittorio Romeo


People also ask

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration.

What is the use of Variadic templates?

With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called parameter pack to represent a list of zero or more parameters for templates.

What is parameter pack in c++?

Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.

What is Pack expansion?

Pack expansion A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order. template<class...


2 Answers

In C++14, the elements of std::tuple are accessible by type provided there are not several elements of the same type. Therefore, you should be able to write it this way:

template<typename... TArgs>
struct Store {

    std::tuple<std::vector<TArgs>...> bags;

    template<typename T>
    void add(T mValue) { 
        get<T>().push_back(mValue); 
    }

    template<typename T>
    std::vector<T>& get() { 
        return std::get<std::vector<T>>(bags); 
    }
};
like image 136
Morwenn Avatar answered Oct 05 '22 15:10

Morwenn


The following should do what you want:

#include <type_traits>
#include <vector>
#include <tuple>
#include <iostream>

// indices are a classic
template< std::size_t... Ns >
struct indices
{
    using next = indices< Ns..., sizeof...( Ns ) >;
};

template< std::size_t N >
struct make_indices
{
    using type = typename make_indices< N - 1 >::type::next;
};

template<>
struct make_indices< 0 >
{
    using type = indices<>;
};

// we need something to find a type's index within a list of types
template<typename T, typename U, std::size_t=0>
struct index {};

template<typename T, typename... Us, std::size_t N>
struct index<T,std::tuple<T,Us...>,N>
: std::integral_constant<std::size_t, N> {};

template<typename T, typename U, typename... Us, std::size_t N>
struct index<T,std::tuple<U,Us...>,N>
: index<T,std::tuple<Us...>,N+1> {};

// we need a way to remove duplicate types from a list of types
template<typename T,typename I=void> struct unique;

// step 1: generate indices
template<typename... Ts>
struct unique< std::tuple<Ts...>, void >
: unique< std::tuple<Ts...>, typename make_indices<sizeof...(Ts)>::type >
{
};

// step 2: remove duplicates. Note: No recursion here!
template<typename... Ts, std::size_t... Is>
struct unique< std::tuple<Ts...>, indices<Is...> >
{
    using type = decltype( std::tuple_cat( std::declval<
        typename std::conditional<index<Ts,std::tuple<Ts...>>::value==Is,std::tuple<Ts>,std::tuple<>>::type
>()... ) );
};

// a helper to turn Ts... into std::vector<Ts>...
template<typename> struct vectorize;

template<typename... Ts>
struct vectorize<std::tuple<Ts...>>
{
    using type = std::tuple< std::vector<Ts>... >;
};

// now you can easily use it to define your Store
template<typename... Ts> class Store
{
    using Storage = typename vectorize<typename unique<std::tuple<Ts...>>::type>::type;
    Storage storage;

    template<typename T>
    decltype(std::get<index<T,typename unique<std::tuple<Ts...>>::type>::value>(storage))
    slot()
    {
        return std::get<index<T,typename unique<std::tuple<Ts...>>::type>::value>(storage);
    }

public:
    template<typename T> void add(T mValue) { 
        slot<T>().push_back(mValue); 
    }

    template<typename T> std::vector<T>& get() { 
        return slot<T>();
    }    
};

int main()
{
    Store<int,int,double,int,double> store;
    store.add(42);
    store.add(3.1415);
    store.add(21);
    std::cout << store.get<int>().size() << std::endl; 
    std::cout << store.get<double>().size() << std::endl; 
}

Live example (without the comments)

like image 32
Daniel Frey Avatar answered Oct 05 '22 15:10

Daniel Frey