2013-10-19 17 views
8

Chcę utworzyć instancję klasy szablonów, która ma numer dla każdego typu w zestawie TArgs....Argumenty kontenera argumentu macierzy szablonów Variadic

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

Załóżmy, że mam Store<int, float, double>. Oczywiście podczas kompilacji wiem, że będzie on w stanie przechowywać wartości int, float i .

mogę użyć specjalizacji szablonu:

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

... ale to wymagałoby ręcznie pisanie każdej możliwej kombinacji typów i nie będzie działać z typów zdefiniowanych przez użytkownika.

Jestem pewien, że kompilator wie wszystko, co jest wymagane do wygenerowania klasy, takiej jak Store<int, float, double> przy użyciu szablonów variadic - czy istnieje sposób na wyrażenie tego zamiaru?

+0

Będziesz potrzebował 'std :: tuple', aby to zrobić,' std :: vector' nie jest odpowiedni. –

+0

Tutaj >> 'std :: array >', kolejność argumentów szablonu jest niepoprawna. Powinien to być 'std :: array ', a nie 'std :: array '. Ponadto, myślę, że potrzebujesz 'std :: tuple', a nie' std :: vector' (nie jestem pewien). – Nawaz

Odpowiedz

9

Poniższa powinien robić to, co chcesz:

#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 (bez uwag)

+0

@VittorioRomeo Zmieniając twoje pytanie, powyższe prawdopodobnie robi * więcej * niż prosiłeś: obsługuje duplikaty typów, dostarczając tylko jeden wektor, stąd 'Store ' zachowuje się tak samo jak 'Store '. Ale wszystkie przypadki, o które prosiłeś, są również automatycznie zakryte :) –

+0

Dziękuję bardzo. Sprytne i skuteczne rozwiązanie! –

5

w C++ 14, elementy std::tuple dostępne są od rodzaju pod warunkiem, że nie ma kilka elementów ten sam typ. Dlatego powinieneś być w stanie napisać to w ten sposób:

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); 
    } 
}; 
+0

Wygląda świetnie, nie mogę się doczekać C++ 14 :) –

Powiązane problemy