7cows
7cows

Reputation: 5044

Create a tuple of maps in a class template

struct VectorOfMaps {
  std::tuple<std::map<std::string,double>,
              std::map<std::string,int>,
              std::map<std::string,short>
            > tuple;
};

I want to generalize this into a template, so the above class would be produced by something like

VectorOfMaps<3, std::string,double,
                std::string,int,
                std::string,short>

Can this, or something similar, be done?

I know I could just put the map types directly into the template, but wouldn't such repetition of "std::map" considered a bit ugly? What do you think?

(I thought about whether I could specify std::pair types in the template, but I was not sure if those could be used directly to create the std::map types) ..

Upvotes: 2

Views: 304

Answers (4)

GreyGeek
GreyGeek

Reputation: 948

I think using std::tuple provides a simpler and cleaner way (but it might be slower...).

template<int pos,typename... Args> struct TypeTransformer_ {
     using ValueType =  typename std::tuple_element<1,std::tuple<Args...>>::type;
     using KeyType =  typename std::tuple_element<0,std::tuple<Args...>>::type ;
     using Type =  decltype(std::tuple_cat(std::tuple<std::map<KeyType,ValueType>>(),
                    typename TypeTransformer_<pos - 2,Args...>::Type()));
};

//Base case with two elements...
template<typename... Args>
struct TypeTransformer_<2,Args...> {
   using ValueType  = typename std::tuple_element<1,std::tuple<Args...>>::type ;
   using KeyType =  typename std::tuple_element<0,std::tuple<Args...>>::type ;
   using Type =  std::tuple<std::map<KeyType,ValueType>> ;
};

//Handling the case of an odd number of parms
template<typename... Args> struct TypeTransformer_<1,Args...> ;

//Nicer interface so we don't have to specify the number of variadic param
template <typename... Args> struct SomeStruct{
using  TupleType = typename TypeTransformer_<sizeof...(Args),Args...>::Type;
TupleType tp ;
};

Upvotes: 0

Luc Danton
Luc Danton

Reputation: 35449

Since you mention passing specializations of pairs to the class template:

template<typename... Pairs>
struct VectorOfMaps {
    std::tuple<std::map<
        typename Pairs::first_type
        , typename Pairs::second_type
    >...> tuple;
};

This is effectively using std::pair as a type-list -- you could use something like template<typename T, typename U> struct pair { using first_type = T; using second_type = U; }; just as well.

It is possible to pass the types without the a pair as well, but this requires some metacomputations. A possible solution:

// First argument is accumulator, supposed to be an empty tuple
template<typename Acc, typename... T> struct compute_tuple
{
    // Only triggered when the primary template is instantiated, which should
    // only happen if sizeof...(T) is odd -- it's possible to assert on that
    // instead, too.
    static_assert( !sizeof(Acc), "An even number of arguments is required" );
};

// Recursive case
template<typename... Acc, typename First, typename Second, typename... Rest>
struct compute_tuple<std::tuple<Acc...>, First, Second, Rest...>
: compute_tuple<std::tuple<Acc..., std::map<First, Second>>, Rest...> {};

// Terminal case
template<typename Acc>
struct compute_tuple<Acc> { using type = Acc; };

template<typename... T>
struct VectorOfMaps {
    /* 
     * You can assert that sizeof...(T) is even here; it might be more
     * helpful than an error deeper inside compute_tuple.
     */
    using tuple_type = typename compute_tuple<std::tuple<>, T...>::type;
    tuple_type tuple;
};

Upvotes: 3

jmihalicza
jmihalicza

Reputation: 2089

The first_of_pair metafunction is unnecessary as pair has first_type and second_type members. Here is the solution with variadic template argument:

template<class... pairtypes>
struct VectorOfMaps
{
    std::tuple<
        std::map<
            typename pairtypes::first_type,
            typename pairtypes::second_type
        >...
    > tuple;
};

int main()
{
    VectorOfMaps<
        std::pair<int,char>,
        std::pair<char,long>
    > v;
    std::get<0>(v.tuple)[4]='a';
    std::get<1>(v.tuple)['z']=5l;
    return 0;
}

Upvotes: 2

jmihalicza
jmihalicza

Reputation: 2089

If you can accept an upper limit on the number of maps to store, you can do it with template specializations for different values of n:

template<int n, class key0, class value0, class key1 = void, class value1 = void>
struct VectorOfMaps;

template<class key0, class value0>
struct VectorOfMaps<1,key0,value0>
{
// ...
};

template<class key0, class value0, class key1, class value1>
struct VectorOfMaps<2,key0,value0,key1,value1>
{
// ...
};

A nicer approach would be using a variadic template parameter.

From a pair argument you can extract the types with helper metafunctions:

template<class T>
struct first_of_pair;

template<class First, class Second>
struct first_of_pair<std::pair<First,Second>>
{
   typedef First type;
};

// usage in a template where T is an std::pair: typename first_of_pair<T>::type

Upvotes: 0

Related Questions