Can this be done using templates in C ++?

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

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 to generalize this type in the following ways:

  • Lines should not be committed three times as many, but any number of lines should be supported as part of the type.

  • I should be able to specify what types should be in each line. Maybe I want double for the first line and int for the second line. (We give this example to a class of two lines.)

  • Finally, I should be able to attach other information to strings than just string . For example, (continuing the example in paragraph 2), I might want to bind string to the first line, but user rss_feed to the second line.

If the templates allowed this (which they don’t have), I would like to print something like this to get my type:

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

number of rows determined from this, or if I really should have:

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

But this is impossible.

Is it possible to do something, and if so, how will I work with this class template? How could I get my vectors and information objects and work with them?

+4
source share
3 answers

This can be done quite easily using std::tuple to specify your type list. All we need to do is declare a primary template to accept two parameters, and then create a partial specialization, where these type parameters are tuples. In a partial specialization, we can use the output of an argument to capture the parameters of a tuple template and reuse them for our purposes. We could create a new template for the purpose of specifying a list of types (i.e. Types<int,double> ), but in this case the tuple is especially good, because in any case you need to have access to separate lines, and std::tuple provides a built-in way to do this with std::get<i> . Using a tuple for template parameters may make it more obvious that you are using std::get to access strings.

Here is 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; } 
+3
source

Using template magic, your problems can be solved by using a variation 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 this field, you can implement the template function get() member

 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 them. The get() methods return a tuple that contains a link to the request fields, and they are only allowed if the past number matches the class template, but what about the other numbers? ok we can turn them on 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 get, // ::template is required to avoid ambiguity } 

But it's not right!!! 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 variational pattern Ts...

But how the hell do we get the types that we want from Ts... , we can find out the types of the variation pattern 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; }; 

therefore variadic_type< 1, int, int, short, short, float, float >::T_type and U_type will be short and short .

Now we have a way to find out the types of a specific place in the variational template, we can apply it to our incorrect get() method to get the correct return type ...

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

We're done, we can call get() as

 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 nice to deal with, but I think it gives an idea.

+11
source

If you know the type (limited set) at compile time, I would just use boost::variant for 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 does not give you enough fixed types for each line that you seem to reference, but it should solve your general problem.

+2
source

Source: https://habr.com/ru/post/1478893/


All Articles