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:
The rows should not be fixed at three anymore, but any number of rows should be supported as a part of the type.
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.)
Finally, I should be able to attach other information than just string
s 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?
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.
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