Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can this be accomplished with templates in C++?

Tags:

c++

This is a very simple class that represents three rows of doubles, with some string information attached to each row:

struct ThreeRows {
  std::vector<double> row1;
  std::vector<double> row2;
  std::vector<double> row3;

  std::string row1info;
  std::string row2info;
  std::string row3info;
};

What I want to do is generalize this type in the following ways:

  1. The rows should not be fixed at three anymore, but any number of rows should be supported as a part of the type.

  2. I should be able to specify what types should be in each row. Maybe I want double for the first row and int for the second row. (Making this example a two row class.)

  3. Finally, I should be able to attach other information than just strings to the rows. For instance (continuing the example in point 2), I may want to attach a string to the first row but a custom rss_feed to the second row.

If templates allowed it (which they don't), I would want to type out something like this to obtain my type:

Rows<{double,int}, {string,rss_feed}>

the number of rows being determined from that, or if I really had to:

Rows<{double,int}, {string,rss_feed}, 2>

But this is not possible.

Can something be done, and if so, how would I work with this class template? How would I actually get at my vectors and info objects and work with them?

like image 742
7cows Avatar asked Dec 04 '22 11:12

7cows


1 Answers

using some template magic, your problems can be solved by variadic template and template recursion.

template< size_t N, typename T, typename U, typename... Ts >
struct Rows : public Rows<N-1, Ts...> {
  vector<T> row;
  U rowinfo;
};

template<typename T, typename U>
struct Rows<1, T, U> {
  vector<T> row;
  U rowinfo;
};

Rows< 3, int, string, float, string, double, rss_feed > rows; 

=>  struct Rows {
       vector<int> v1;
       string s1;
       vector<foat> v2;
       string s2;
       vector<double> v3;
       rss_feed feed;
    }

to access the value of those field, you can implement a templated get() member function

template< size_t N, typename T, typename U, typename... Ts >
struct Rows : public Rows<N-1, Ts...> {
  vector<T> row;
  U rowinfo;

  template< size_t M >
  typename enable_if< M == N, tuple<vector<T>&, U&> >::type
  get() {
    return make_tuple( ref(row), ref(rowinfo) );
  }   
};

template<typename T, typename U>
struct Rows<1, T, U> {
    vector<T> row;
    U rowinfo;

    template< size_t M >
    typename enable_if< M == 1, tuple<vector<T>&, U&> >::type
    get() {
        return make_tuple( ref(row), ref(rowinfo) );
    }

};

take a moment and digest those. The get() methods return a tuple that contains reference to the request fields, and they are only enabled if the passed in number match with the class's template, what about other numbers? well we can enable them like this

template< size_t M >
typename enable_if< M != N, tuple<vector<T>&, U&> >::type
get() {
    return Rows<N-1, Ts...>::template get<M>();  // call parent's get,  
                                                 // ::template is required to avoid ambiguity

} 

but this is wrong!!! The return type is not tuple<vector<T>&, U&>, it should be tuple<vector<A>&, B&> where A and B are the corresponding types in the variadic template Ts...

But how the hell do we get the types we want from Ts..., well we can find out the types of a variadic template with the following technique.

template< size_t N, typename... Ts >
struct variadic_type;

template< typename T, typename U, typename... Ts >
struct variadic_type< 0, T, U, Ts... > {
    typedef T T_type;
    typedef U U_type;
};

template< size_t k, typename T, typename U, typename... Ts >
struct variadic_type< k, T, U, Ts... > {
    typedef typename variadic_type< k-1, Ts... >::T_type T_type;
    typedef typename variadic_type< k-1, Ts... >::U_type U_type;
};

so variadic_type< 1, int, int, short, short, float, float >::T_type and U_type would be short and short.

Now we have a way to find out the types of a specific location on the variadic template, we can apply it to our wrong get() method to get the correct return type...

template< size_t M >
typename
    enable_if< M != N,
                tuple<
                    vector<typename variadic_type< N-M, T, U, Ts...>::T_type>&,
                    typename variadic_type< N-M, T, U, Ts...>::U_type&
                > >::type
get() {
    return Rows<N-1, Ts...>::template get<M>();
}

We are done, we can call get() like

Rows< 3, int, string, double, string, float, string > rows;

auto r3 = rows.get<3>();  //  { vector<int>& v, string& s };
auto r2 = rows.get<2>();  //  { vector<double>& v, string& s };
auto r1 = rows.get<1>();  //  { vector<float>& v, string& s };

auto r4 = rows.get<4>();  //  error:: other numbers will not compile!!

now the index rule is not very pleasant to deal with, but I think this gets the idea across.

like image 107
yngccc Avatar answered Dec 20 '22 12:12

yngccc