The beauty of generalization--c++11 the magical magic of variable template Parameters 1 overview
The new feature of c++11-the variable template parameter (variadic templates) is one of the most powerful features of C++11, which is highly generalized for parameters that represent 0 to any number of parameters of any type. Compared to c++98/03, the class template and function template can only contain a fixed number of template parameters, variable template parameters is undoubtedly a huge improvement. However, due to the fact that the variable template parameters are abstract, it is necessary to use some skills, so it is one of the most difficult to understand and master in C++11. Although it is difficult to master the variable template parameters, but it is one of the most interesting features of C++11, this article hopes to lead readers to understand and master this feature, and also through some examples to demonstrate the use of variable parameter template.
2 expansion of variable template parameters
The semantics of the variadic template and the Normal template are the same, except that the wording is slightly different, and the variable parameter template needs to be appended with the ellipsis "..." after the TypeName or class. For example, we often declare a variable template parameter:template<typename...> or template<class...> the definition of a typical variable template parameter is this:
Template <class ... T>void f (T ... args);
In the definition of the above variable template parameter, the ellipsis has a function of two:
1. Declare a parameter package T ... args, which can contain 0 to any of the template parameters;
2. On the right side of the template definition, the parameter package can be expanded into a single parameter.
The above parameter, args, is preceded by an ellipsis, so it is a variable template parameter, and we refer to the parameter with the ellipsis as a "parameter Packet", which contains 0 to N (n>=0) template parameters. We can not directly get the parameter package args in each parameter, only by expanding the parameter package to get each parameter in the parameter package, which is a key feature of the use of variable template parameters, but also the biggest difficulty, that is, how to expand the variable template parameters.
The variable template parameters and the Normal template parameter semantics are consistent, so it can be applied to functions and classes, variable template parameter functions and variable template parameter classes, however, the template function does not support the partial localization, so the variable template parameter functions and variable template parameter classes expand variable template parameters are not the same method, Let's take a look at their methods of expanding the variable template parameters separately.
2.1 Variable Template parametric functions
A simple variable template parameter function:
Template <class ... T>void f (T ... args) { cout << sizeof ... (args) << Endl; Print the number of arguments}f (); 0f (1, 2); 2f (1, 2.5, ""); 3
In the above example, F () has no parameters, so the parameter package is empty, the output size is 0, the subsequent two calls are passed in two and three parameters respectively, so the output size is 2 and 3 respectively. Since the type and number of variable template parameters are not fixed, we can pass any type and number of arguments to the function f. This example simply prints out the number of variable template parameters, and if we need to print out each parameter in the parameter pack, we need to pass some methods. There are two ways to expand a variable-template parametric function: One is to expand the parameter package by using a recursive function, and the other is to expand the parameter package by using a comma-expression. Let's take a look at how to expand the parameter pack using both of these methods.
2.1.1 A recursive function to expand a parameter package
To expand a parameter package with a recursive function, you need to provide a function for the expansion of a parameter pack and a recursive termination function, which is used to terminate the recursion, to see the following example.
#include <iostream>using namespace std;//recursive termination function void print () { cout << "Empty" << Endl;} Expand function Template <class T, class ... Args>void Print (T head, Args ... rest) { cout << "parameter" << head << Endl; Print (rest ...); int main (void) { print (1,2,3,4); return 0;}
The previous meeting outputs each parameter until empty. The function that expands the parameter package has two, one is recursive function, the other is recursive terminating function, parameter package args ... Recursively calls itself during the unfolding process, with fewer parameters per call to the parameter pack, until all parameters are expanded, and when no arguments are called, the non-template function print terminates the recursive process.
The procedure for recursive invocation is this:
Print (1,2,3,4);p rint (2,3,4);p rint (3,4);p rint (4);p rint ();
The recursive termination function above can also be written like this:
Template <class t>void print (T t) { cout << t << Endl;}
After modifying the recursive termination function, the invocation procedure in the previous example is this:
Print (1,2,3,4);p rint (2,3,4);p rint (3,4);p rint (4);
When the parameter package expands to the last parameter, the recursion is reached. Let's look at an example of summing with a variable template parameter:
Template<typename t>t sum (t t) { return t;} Template<typename T, TypeName ... Types>t sum (T First, Types ... rest) {return First + sum<t> (rest ...);} SUM (1,2,3,4); 10
Sum adds each parameter in the process of expanding the parameter package, and the parameters are expanded in the same way as the previous print parameter package.
2.1.2 comma-expression expansion parameter pack
The recursive function expansion of the parameter package is a standard practice, but also a good understanding, but there is a drawback, it is necessary to have an overloaded recursive termination function, that is, you must have a terminating function with the same name to terminate recursion, which may feel a little inconvenience. Is there a simpler way? In fact, there is a way to expand the parameter package without recursion, which requires the use of a comma expression and an initialization list. For example, the previous print examples can be changed to this:
Template <class t>void printarg (T t) { cout << t << Endl;} Template <class ... Args>void expand (args ... args) { int arr[] = {(Printarg (args), 0) ...};} expand (1,2,3,4);
This example will print out the 1,2,3,4 four numbers separately. This way of expanding the parameter package, does not need to pass the recursive termination function, is directly in the expand function body expansion, PRINTARG is not a recursive termination function, just a function that handles each parameter in the parameter package. The key to implementing this in-place expansion of the parameter package is the comma expression. We know that the comma expression executes the expression preceding the comma sequentially, such as:
This expression is executed sequentially: B is assigned to a, then the comma expression in parentheses returns the value of C, so D will be equal to C.
The comma expression in the expand function: (Printarg (args), 0), which is executed in this order, first executes the printarg (args), and then the result of the comma expression 0. It also uses another feature of c++11-initialization list, initializing a variable-length array by initializing the list, {(Printarg (args), 0) ...} will be expanded into ((Printarg (arg1), 0), (Printarg (ARG2), 0), (Printarg (ARG3), 0), etc ...) and will eventually create an array of element values of 0 int arr[sizeof ... (Args)]. Because it is a comma expression, in the process of creating an array, the first part of Printarg (args) is executed before the comma expression to print out the parameters, that is, in the process of constructing an int array, the parameter package is expanded, the purpose of this array is purely to expand the parameter package in the process of array construction. We can further improve the above example, the function as a parameter, you can support the lambda expression, so you can write a recursive termination function, the specific code is as follows:
Template<class F, class ... Args>void expand (const f& F, Args&&...args) { //The perfect forwarding is used here, the reader can refer to the author's article in the previous issue of the programmer, "see Rvalue Reference by 4 lines of code "Initializer_list<int>{(f (std::forward< args> (Args)), 0) ...};} expand ([] (int i) {cout<<i<<endl;};
The above example prints out each parameter, where you can write a more generalized lambda expression if you use the new c++14 generic lambda expression:
expand ([] (auto i) {Cout<<i<<endl;}, 1,2.0, "test");
2.2 Variable template parameter classes
The Variadic template class is a template class with variable template parameters, such as the Ganso std::tuple in c++11 is a mutable template class, which is defined as follows:
template< class ... Types >class tuple;
This variable-parameter template class can carry any number of template parameters of any type:
Std::tuple<int> TP1 = std::make_tuple (1); Std::tuple<int, double> TP2 = std::make_tuple (1, 2.5); std::tuple <int, double, string> TP3 = std::make_tuple (1, 2.5, "");
The variable parameter template can have 0 template parameters, so the following definitions are also valid:
std::tuple<> TP;
The parameter package expansion of variable parameter template class is different from that of variable parameter template function, the parameter package expansion of variable parameter template class needs to be expanded by template special and inheriting mode, and the expansion mode is more complex than the variable parameter template function. Let's take a look at the method of expanding the parameter package in the Variadic parameter class.
2.2.1 Template biasing and recursion to expand parameter packages
The expansion of a Variadic template class typically involves defining two to three classes, including class declarations and a templated class that is biased. A basic mutable parameter template class is defined as follows:
Forward declaration Template<typename ... Args>struct sum;//Basic Definition Template<typename First, TypeName ... Rest>struct Sum<first, rest...>{ enum {value = Sum<first>::value + Sum<rest...>::value};};/ /recursive termination template<typename last>struct sum<last>{ enum {value = sizeof (last)};
The function of this sum class is to compute a size sum of the parameter types in the parameter package at compile time, which can be obtained by sum<int,double,short>::value the sum of the 3 types of size 14. This is a simple example of the variable parameter template class calculation, you can see a basic variable parameter template application class consists of three parts, the first part is:
Template<typename ... args> struct SUM
It is a forward declaration that declares that the sum class is a mutable parameter template class, and the second part is the definition of the class:
Template<typename First, TypeName ... Rest>struct Sum<first, rest...>{ enum {value = Sum<first>::value + Sum<rest...>::value};};
It defines a partially expanded variable-mode parameter template class that tells the compiler how to recursively expand a parameter package. The third part is the recursive termination class of the Special:
Template<typename last> struct sum<last>{ enum {value = sizeof (first)};}
To terminate recursion through this special class:
Template<typename First, TypeName ... Args>struct sum;
This forward declaration requires at least one template parameter for sum, because the template parameters in the Variadic template can have 0, and sometimes 0 template parameters do not make sense, you can limit the template parameters to 0 by the above declaration method. The above-mentioned three-paragraph definition can also be changed to two-paragraph, you can remove the forward declaration, so the definition:
Template<typename First, TypeName ... Rest>struct sum{ Enum {value = Sum<first>::value + Sum<rest...>::value};}; Template<typename last>struct sum<last>{ enum{value = sizeof (last)};};
The above method is as long as a basic template class definition and a special termination function, and limit the template parameters at least one.
Recursive terminating template classes can be written in many ways, such as the recursive termination template class in the previous example:
Template<typename ... args> struct Sum;template<typename First, typenamelast>struct Sum<first, last>{ enum{value = sizeof (first) +sizeof (last)};};
Terminates when expanding to the last two parameters.
You can also terminate when expanding to 0 parameters:
Template<>struct sum<> {enum{value = 0};};
You can also use Std::integral_constant to eliminate the enumeration definition value. The std::integral_constant can be used to obtain the characteristics of the compile-time constants, you can change the previous sum example to this:
Forward Statement Template<typename First, TypeName ... Args>struct sum;//Basic Definition Template<typename First, TypeName ... Rest>struct Sum<first, rest...>: std::integral_constant<int, Sum<first>::value + Sum<Rest ... >::value>{};//recursive termination template<typename last>struct sum<last>: std::integral_constant<int, sizeof (last) >{};sum<int,double,short>::value;//value is 14
2.2.2 Inheritance mode expand parameter Pack
You can also expand the parameter package by inheritance, such as the following example is the way to expand the parameter package by inheritance:
Definition of integer sequence template<int ... >struct indexseq{};//inherit mode, start expand parameter Package template<int N, int ... Indexes>struct makeindexes:makeindexes<n-1, N-1, indexes...> {};//Template special, terminating the condition template<int the parameter pack ... Indexes>struct makeindexes<0, indexes...>{ typedefindexseq<indexes...> type;}; int main () { using T = makeindexes<3>::type; cout <<typeid (T). Name () << Endl; return 0;}
The function of makeindexes is to generate an integer sequence of a mutable parameter template class, and the final output type is: struct indexseq<0,1,2>.
Makeindexes inherits from its own special template class, this special template class is also expanding the parameter package, this expansion process is initiated through inheritance, until the completion of the termination of the special conditions to expand the process to end. The Makeindexes<1,2,3>::type process is as follows:
Makeindexes<3>: Makeindexes<2, 2>{}makeindexes<2, 2>: makeindexes<1, 1, 2>{}MakeIndexes<1 , 1, 2>: makeindexes<0, 0, 1, 2>{ typedef indexseq<0, 1, 2> type;}
Through the continuous succession of recursive calls, the end result is the integer sequence indexseq<0, 1, 2>.
If you do not want to generate the shaping sequence by inheritance, you can build it in the following way.
Template<int N, int ... Indexes>struct makeindexes3{ Using type = TypeName makeindexes3<n-1, N-1, indexes...>::type;}; Template<int ... Indexes>struct makeindexes3<0, indexes...>{ typedef indexseq<indexes...> type;};
We see how we can use recursion and partial specificity to expand the variable template parameters, so how do we do it in practice? We can use the variable template parameters to eliminate some of the duplicated code and implement some advanced features, let's look at some of the variable template parameters of the application.
3 variable parameter template de-duplication code
Before c++11 if you were to write a generalized factory function that would accept any type of argument, and if the number of parameters would satisfy most of the application requirements, we would have to define a lot of duplicate template definitions, such as the following code:
Template<typename t>t* Instance () { return new T ();} Template<typename T, TypeName t0>t* Instance (T0 arg0) { return new T (arg0);} Template<typename T, TypeName T0, TypeName t1>t* Instance (T0 arg0, T1 arg1) { return new T (arg0, arg1);} Template<typename T, TypeName T0, TypeName T1, TypeName t2>t* Instance (T0 arg0, T1 arg1, T2 arg2) { return new T ( arg0, Arg1, arg2);} Template<typename T, TypeName T0, TypeName T1, TypeName T2, TypeName t3>t* Instance (T0 arg0, T1 arg1, T2 arg2, T3 ar G3) { return new T (arg0, Arg1, arg2, ARG3);} Template<typename T, TypeName T0, TypeName T1, TypeName T2, TypeName T3, TypeName t4>t* Instance (T0 arg0, T1 arg1, T 2 arg2, T3 arg3, T4 arg4) { return new T (arg0, Arg1, arg2, Arg3, ARG4);} struct a{ A (int) {}};struct b{ B (int,double) {}}; A * pa = instance<a> (1); b* PB = instance<b> (UP);
You can see that this generic factory function has a large number of duplicate template definitions, and the template parameters are qualified. With variable template parameters you can eliminate the repetition, and remove the limit of the number of parameters, the code is very concise, through the variable parameter templates optimized factory functions are as follows:
template<typename ... args>t* Instance (args&&. args) { return new T (std::forward<args> (args) ...);} A * pa = instance<a> (1); b* PB = instance<b> (UP);
4 variable parameter template for generalization delegate
There are no C #-like delegates in C + +, and we can implement one with variable template parameters. The basic usage of delegates in C # is this:
delegate int aggregatedelegate (int x, int y);//Declaration delegate type int ADD (int x, int y) {return x+y;} int Sub (int x, int y) {return x-y;} Aggregatedelegate add = add;add;//Call Delegate object sum Aggregatedelegate sub = sub;sub (2,1);//Call Delegate Object subtraction
The use of Delegates in C # requires defining a delegate type that cannot be generalized, that is, once a delegate type is declared, it can no longer be used to accept other types of functions, such as:
int fun (int x, int y, int z) {return x+y+z;} int Fun1 (string s, String r) {return s.length+r.length;} Aggregatedelegate fun = fun; Compile error, can only assign values of the same type of function aggregatedelegate fun1 = fun1;//Compile error, parameter type mismatch
The reason why this can not be generalized is that when declaring a delegate type, the parameter type and number are limited, there is no problem in c++11, because there is a variable template parameter, it represents any type and number of parameters, let's look at how to implement a more generalized C + + Version of the delegate (here, for the sake of simplicity, only the case of member functions, and ignoring the processing of const, volatile, and const volatile member functions).
Template <class T, class R, TypeName ... Args>class mydelegate{public: mydelegate (t* T, R (t::* f) (Args ...)): m_t (T), M_f (f) {} R operator ( (Args&&.. args) { return (M_t->*m_f) (std::forward<args> (args) ...); } Private: t* m_t; R (T::* m_f) (Args ...);}; Template <class T, class R, TypeName ... Args>mydelegate<t, R, args...> createdelegate (t* T, R (t::* f) (Args ...) { return mydelegate<t, R, args...> (T, f);} struct a{ void fun (int i) {Cout<<i<<endl;} void Fun1 (int i, double j) {cout<<i+j<<endl;}}; int main () { a A; Auto D = createdelegate (&a, &a::fun); Create Delegate D (1);//Invoke delegate, output 1 auto D1 = createdelegate (&a, &A::FUN1);//Create Delegate D1 (1, 2.5);//Invoke Delegate, Will output 3.5}
The key to the implementation of MyDelegate is the definition of a "universal function" that accepts any type and number of parameters: R (T::* m_f) (Args ...), because of the characteristics of the variable template parameters, so that we can let the M_f accept arbitrary parameters.
5 Summary
These techniques of using variable template parameters believe that the reader will see a refreshing feeling, the key to the use of variable template parameters is how to expand the parameter package, the process of expanding the parameters package is very subtle, embodies the beauty of generalization, recursion beauty, it is because it has magical "magic", so we can more generalized to deal with the problem, For example, it is used to eliminate duplicate template definitions, and it is used to define a "universal function" that can accept arbitrary parameters. In fact, the role of variable template parameters far more than the list of those roles, it can also be combined with other c++11 features, such as Type_traits, Std::tuple and other features, play a more powerful power, will be introduced in the application of template meta-programming.
-c++11 Variable template parameters (reprint)