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?
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.
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.
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.
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...
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);
}
};
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With