4

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?

7cows
  • 4,974
  • 6
  • 25
  • 30
  • 1
    It seems feasible with variadic templates if you can use c++11 and some preprocessor fun with c++03. `struct rows< std::pair, std::pair, std::pair, ... >` It's essentially a type of `std::tuple`. – Brandon May 03 '13 at 00:47
  • 6
    Everything can be done with templates in C++... EVERYTHING. – Richard J. Ross III May 03 '13 at 01:00
  • 1
    Have a look at [Boost.Fusion](http://www.boost.org/doc/libs/release/libs/fusion/doc/html/index.html). – Mankarse May 03 '13 at 01:05

3 Answers3

11

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.

yngccc
  • 5,594
  • 2
  • 23
  • 33
  • 1
    Alternatively, if you wrapped the 2 related fields into a class you could have done it with your original implementation. – David D May 03 '13 at 01:01
  • 1
    This is an interesting answer. I tried to compile your implementation but i get the following compile time error = "wrong number of template arguments (2, should be 3 or more)" associated with the line `struct Rows : public Rows {`. – deepak May 03 '13 at 01:33
  • 1
    @deepak compiles fine for me, for the above example only 3 will work. – yngccc May 03 '13 at 01:38
  • @yngum But how do I "implement a function similar to std::get(std::tuple)"? Seems to me that such a function would somehow have to get at the members of the superclasses (the member names, "row" and "rowinfo", being the same in all the classes in the hierarchy) correct? I wonder how that could be done. – 7cows May 03 '13 at 10:15
  • 2
    Another day, another *awesome* answer on StackOverfow. – Nik Bougalis May 03 '13 at 18:25
  • @yngum Wow if this works (which I'm sure it does) this is insane. It's gonna take a while to digest :( It's not like the usual C++ coding. Where did you pick up this technique? Can you recommend sources/books? Is this so-called metaprogramming? – 7cows May 03 '13 at 20:30
  • 1
    @7cows I tested on clang and it worked. [this thread](http://stackoverflow.com/questions/112277/best-introduction-to-c-template-metaprogramming) talk about books to learn C++ metaprogramming, if you are interested. – yngccc May 03 '13 at 21:25
3

This can be done fairly easily using a std::tuple to specify your list of types. All we need to do is declare the primary template to take two parameters, then create a partial specialization where those type parameters are tuples. In the partial specialization, we can use the argument deduction to capture the template parameters of the tuple and re-use them for our purposes. We could create a new template for the purposes of specifying the list of types (i.e. Types<int,double>), but a tuple is especially nice in this case because you need to have a way to access the the individual rows anyway, and a std::tuple provides a built-in way to do this through std::get<i>. Using the tuple for the template parameters may make it more obvious that you use std::get to access the rows.

Here's a complete example:

#include <string>
#include <tuple>
#include <vector>

// primary template
template <typename RowTuple,typename RowInfoTuple> struct Rows;

// variadic partial specialization 
template <typename... RowTypes,typename... RowInfoTypes>
struct Rows<std::tuple<RowTypes...>,std::tuple<RowInfoTypes...>>
{
  // use variadic expansion to make a tuple of vectors
  std::tuple<std::vector<RowTypes>...> rows;
  std::tuple<RowInfoTypes...> rowinfos;
};

struct rss_feed { };

int main(int,char**)
{
  Rows<
    std::tuple<double,int>,
    std::tuple<std::string,rss_feed>
  > data;

  std::get<0>(data.rows).push_back(1.5);
  std::get<1>(data.rows).push_back(2);
  std::get<0>(data.rowinfos) = "info";
  std::get<1>(data.rowinfos) = rss_feed();
  return 0;
}
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • That's even better (and simpler) than yngum's solution! Is his solution reinventing the tuple wheel? Another thing: Like yngum does (for a different purpose), you first declare a class Rows and below you define it. Is this a commonly used trick when using templates? When is this usually used? Can you elaborate? – 7cows May 04 '13 at 08:33
  • 1
    @7cows: I've expanded my answer a bit to talk about partial specialization. I would say that partial specialization is fairly common. It is very powerful. It is the mechanism behind all the std classes that give you information about types that are defined in the header. It is perhaps a bit less common to have only one specialization of the primary template. – Vaughn Cato May 04 '13 at 15:11
  • 1
    @7cows: In this case, we use it to get around the fact that you can't do more sophisticated argument specifications in the primary template, such as `template ,class std::tuple >`. – Vaughn Cato May 04 '13 at 15:15
2

If you know the (limited set of) types at compile time I would just use boost::variant to do this:

typedef boost::variant<double, int> DataType;
typedef boost::variant<string, rss_feed> MetaDataType;

struct Row
{
    DataType data_;
    MetaDataType meta_data_;
};

template <int NUM_ROWS>
struct Rows
{
    Row row_data_[NUM_ROWS];
};

This doesn't give you quite the fixed types per row you seem to allude to but it should solve your general problem.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • This is less typesafe than it could be. We know that one of the two rows should hold double, and the other int, and we could enforce that using the type system, but your example would allow both rows to store doubles or both rows to store ints. – HighCommander4 Aug 30 '13 at 04:04