Template
_Args> _Args>
To clarify, I am using the GCC7.1.0 compiler, and the standard library source code is also in this version.
As usual, take a look at the outline of this article, which is as follows:
Vector and deque code: vector and deque code:
template<typename. _Args>void emplace_front(_Args&&... __args);
Copy the code
Template
, this is the variable parameter template, and its parameters are also special _Args&&… __args, without rvalue references, is a mutable argument, so what are mutable argument templates and mutable arguments, and how to use them, we’re going to dig into those things today.
1. What is a variable parameter template
A variable parameter template is a template that can change the number and type of its parameters. To do this, you must use a template parameter package.
Template parameter package is acceptable zero or template parameter n template argument, at least one template parameter package template variable parameters can be called templates, so in plain English, understand the template parameter packets will understand the variable parameter template, because of the variable parameter template is implemented based on template parameter package, then we take a look at exactly what is the template parameters.
2. Variable parameter template base – template parameter package
Template parameter packages mainly appear in function templates and class templates. At present, there are three types of template parameter packages: non-type template parameter packages, type template parameter packages, and template template parameter packages.
2.1 Parameter package of untyped Templates
The syntax for a nontype template parameter package looks like this:
template< type... args>Copy the code
The typename keyword and the class keyword are used to indicate that a template uses the typename keyword or the class keyword to indicate that it is followed by the typename. The type in the parameter package represents a fixed type, so it’s better to call it a fixed type template parameter package.
For the above non-type template parameter package, the type is a fixed type, and args is actually a modifiable parameter name, as follows:
template<int. data> xxxxxx;Copy the code
Note that this fixed type is limited to integers, Pointers, and references, as specified in standard c++.
For example, if I want to count the total ages of the children in this kindergarten, but I don’t know how many children there are, then I can use this non-type template parameter package as follows:
#include <iostream>
using namespace std;
PrintAmt
(int&) = printAmt
(int&)
PrintAmt <>(int&) is called. The template argument is empty, but the template parameter list cannot be empty
template<class type>
void printAmt(int &iSumAge)
{
return;
}
template<class type, int age0, int. age>void printAmt(int &iSumAge)
{
iSumAge += age0;
// sizeof... (age) counts the number of parameters in the parameter package. The return type is STD ::size_t, and so on
if((sizeof. (age)) >0 )
{
// where age... It's actually a package expansion in syntax, as I'll explain later
printAmt<type, age...>(iSumAge);
}
}
int main(a)
{
int sumAge = 0;
printAmt<int.1.2.3.4.5.7.6.8>(sumAge);
cout << "the sum of age is " << sumAge << endl;
return 0;
}
Copy the code
This is just an example to illustrate the use of non-type template parameter packages. In a real project, there is no need to write a template for something so simple.
Based on the syntax and code usage, we summarize the non-type template parameter package as follows:
- The package types of nontype template parameters are fixed, but the parameter names can be changed just like ordinary function parameters.
- The arguments passed to the nontype template parameter package are not types, but actual values.
2.2 Type Template Parameter package
The syntax for the type template parameter package is as follows:
typename|class.Args
Copy the code
The typename keyword and the class keyword can be used to declare an unknown type in a template. The typename keyword and the class keyword can be used to declare an unknown type in a template. The typename keyword and the class keyword can be used to declare an unknown type in a template. The deformable parameter package can accept an infinite number of different argument types.
Now let’s take a look at the type template parameter package. Suppose we have a scenario where I want to output a person’s name, gender, age, height, and other personal information, but we are not sure what information is available. What should we do?
We can use the type template parameter package, as shown in the following code:
#include <iostream>
using std::cout;
using std::endl;
void xprintf(a)
{
cout << endl;
}
template<typename T, typename. Targs>void xprintf(T value, Targs... Fargs)
{
cout << value << ' ';
if((sizeof. (Fargs)) >0 )
{
// The template is not explicitly specified here because function templates can be derived automatically from function arguments
xprintf(Fargs...) ; }else
{
xprintf();
}
}
int main(a)
{
xprintf(Personal Information of Xiao Ming:."Xiao Ming"."Male".35."Programmer".169.5);
return 0;
}
Copy the code
The following output is displayed:
Xiao Ming's personal information: Xiao Ming male 35 programmer 169.5Copy the code
This is a typical use of a type template parameter package in a function template, as you can see,
Of course, some people might say that cout can be done in a single line of code, but we are providing a generic interface, and we do not know what to output beforehand, so it is convenient to use type template parameter packages.
2.3 Template Template parameter package
The template parameter package is a bit confusing, so let’s look at the syntax:
template< parameter list >class.Args(optional)Copy the code
The parameter package itself is a template. Before we look at the template parameter package, we will introduce the template parameter package. Because the parameter package is explicitly implemented by adding ellipsis on the basis of the parameter.
Let’s take a look at the library’s use of template template parameters and find the bits/alloc_traits. H header, which has a structure in the template class allocator_traits declaration:
template<template<typename> class _Func.typename _Tp>
struct _Ptr<_Func, _Tp, __void_t<_Func<_Alloc>>>
{
using type = _Func<_Alloc>;
};
Copy the code
The _Func template parameter is itself a template type that needs to be declared as a template argument.
Suppose we have a scenario where we need to define a vector variable, but cannot determine the element type of a vector?
Look at the following code:
#include <typeinfo>
#include <cxxabi.h>
#include <iostream>
#include <vector>
// Translate the compiled types of GCC into real types
const char* GetRealType(const char* p_szSingleType)
{
const char* szRealType = abi::__cxa_demangle(p_szSingleType, nullptr.nullptr.nullptr);
return szRealType;
}
// func is a template template parameter
template<template<typename.typename> class func.typename tp.typename alloc = std::allocator<tp> >
struct temp_traits
{
using type = func<tp, alloc>;
type tt;Define a member variable based on the template type
};
int main(a)
{
temp_traits<std::vector, int> _traits;
// Get the type of the structure field tt
const std::type_info &info = typeid(_traits.tt);
std::cout << GetRealType(info.name()) << std::endl;
return 0;
}
Copy the code
The following output is displayed:
std::vector<int, std::allocator<int> >
Copy the code
Here, we use the template template parameter and other template parameters for the type temp_tratis.
The template template parameter package is the same as the type template parameter package, but the type is changed into a template type.
#include <typeinfo>
#include <cxxabi.h>
#include <iostream>
#include <vector>
#include <deque>
#include <list>
// Translate the compiled types of GCC into real types
const char* GetRealType(const char* p_szSingleType)
{
const char* szRealType = abi::__cxa_demangle(p_szSingleType, nullptr.nullptr.nullptr);
return szRealType;
}
// Generalize the argument template
template<typename tp, typename alloc, template<typename.typename> class.types >
struct temp_traits
{
temp_traits(tp _tp)
{
std::cout << "Generic template Execution"<< std::endl; }};// Partial parameter template
template< typename tp, typename alloc, template<typename.typename> class type.template<typename.typename> class.types >
struct temp_traits<tp, alloc,type, types... > :publictemp_traits<tp, alloc, types... > {using end_type = type<tp, alloc>;
end_type m_object;
temp_traits(tp _tp) :temp_traits<tp, alloc, types... >(_tp) {const std::type_info &info = typeid(m_object);
std::cout << "Partial specialized version executes, when type :" << GetRealType(info.name()) << std::endl;
m_object.push_back(_tp);
}
void print(a)
{
auto it = m_object.begin(a);for(; it ! = m_object.end(a); ++it) { std::cout <<"Type :" << GetRealType(typeid(end_type).name()) < <", the data is:<< *it << std::endl; }}};int main(a)
{
temp_traits<int, std::allocator<int>, std::vector, std::deque, std::list> _traits(100);
_traits.print(a);return 0;
}
Copy the code
This code is pretty hard to understand, and we can think of it as recursive inheritance, but what is recursive inheritance? You can start by looking at the results of the execution and work backwards through the results.
Let’s have a look at the result, as follows:
The generalized template executes the specialized version of STD ::__cxx11::list<int, STD ::allocator<int> > the specialized version of STD ::deque<int, STD ::allocator<int, STD ::deque<int, STD ::allocator<int> > The specialized version is executed when the type is STD ::vector<int, STD ::allocator<int> > and the type is STD ::vector<int, STD ::allocator<int>. The data is: 100Copy the code
According to four constructor calls, we may safely draw the conclusion: parameter package contains many parameters, it will be on the basis of several layers of inheritance, so now is three parameters, three layers of inheritance, the top base class is a generic template, then the three layer is derived, and the recursive inheritance is the process of the compiler according to the code itself.
Look at the call for the member function print, my original intention was to for each container type, print out the results, but now only print a, we can think about it, for the inheritance, the virtual function but function under the condition of same type, the members of the derived class functions will cover the base class member function, so the result is normal here.
The member function print is a layer destructor, so change it to the following code:
~temp_traits()
{
auto it = m_object.begin(a);for(; it ! = m_object.end(a); ++it) { std::cout <<"Type :" << GetRealType(typeid(end_type).name()) < <", the data is:<< *it << std::endl; }}Copy the code
The output is as follows:
The generalized template executes the specialized version of STD ::__cxx11::list<int, STD ::allocator<int> > the specialized version of STD ::deque<int, STD ::allocator<int, STD ::deque<int, STD ::allocator<int> > The specialized version is executed when the type is STD ::vector<int, STD ::allocator<int> > and the type is STD ::vector<int, STD ::allocator<int>. Data :100 Type: STD ::deque<int, STD ::allocator<int> > Data :100 Type: STD ::__cxx11::list<int, STD ::allocator<int> > Data :100Copy the code
At this point, we should have a better understanding of the template template parameter package.
Note that regardless of the parameter package, the parameter package needs to be placed at the end of the template, otherwise compilation will be problematic.
3. Extension of template parameter package – function parameter package
We all know what a function parameter is, but what about a function parameter package? Let’s look at the syntax of a function parameter package:
Args ... args
Copy the code
Where the Args… This type is declared in the template parameter package. Args is the parameter name of the function. It can be customized.
So is all the template parameter package statement type can be used as a function parameter package type, no, we talked about the three kinds of template parameters in front of the bag, which in addition to the type of template parameters bag because fixed type and is specific value, not as a function parameter package, type template parameter bales and template template parameters because the statement are all types, So they are types that can be used as function parameters.
As we explained in section 2.2, let’s look at how template row parameters can be used as function parameters:
#include <typeinfo>
#include <cxxabi.h>
#include <iostream>
#include <vector>
#include <list>
#include <deque>
// Translate the compiled types of GCC into real types
const char* GetRealType(const char* p_szSingleType)
{
const char* szRealType = abi::__cxa_demangle(p_szSingleType, nullptr.nullptr.nullptr);
return szRealType;
}
void xprintf(a)
{
std::cout << "Calling an empty function" << std::endl;
}
template<typename tp, typename alloc, template<typename.typename> class T.template<typename.typename> class.Targs >
void xprintf(T<tp, alloc> value, Targs<tp, alloc>... Fargs)
{
std::cout << "Container type :" << GetRealType(typeid(value).name()) << std::endl;
std::cout << "Container data :" << std::endl;
auto it = value.begin(a);for(; it ! = value.end(a); ++it) { std::cout << *it <<', ';
}
std::cout << std::endl;
if((sizeof. (Fargs)) >0 )
{
// The template is not explicitly specified here because function templates can be derived automatically from function arguments
xprintf(Fargs...) ; }else
{
xprintf();
}
}
int main(a)
{
std::vector<int> vt;
std::deque<int> dq;
std::list<int> ls;
for(int i =0 ; i < 10 ; ++i)
{
vt.push_back(i);
dq.push_back(i);
ls.push_back(i);
}
xprintf(vt, dq, ls);
return 0;
}
Copy the code
This is a typical use of template template parameter package type as a case, the function parameters in plain English, we want to understand the nature of the function form and package, it is really a function parameter, since it is the function parameter, cannot leave the type parameter name syntax, parameter package is a ellipsis is put behind the type, When the template parameter package is used as a function parameter type, you must remember to add template parameters, such as T
in the code, so that a complete type, T alone, its type is incomplete.
With this in mind, we have no difficulty in using function parameter packages.
4. Expansion method of template parameter package
What is a parameter package expansion? Let’s look at the syntax:
Mode...Copy the code
The package expands by adding an ellipsis after the schema, which is usually the name of the parameter package or a reference to the parameter package. The package expands to zero or more comma-separated arguments.
Like the age above… And Fargs… Both belong to package expansion, but remember that there is no direct way to use this form, so how to use it, there are two ways:
- One is to recurse the parameters in the parameter package one by one, and end the recursion with a default function or specialized template class.
- The second is to expand the parameter package directly and pass it to the appropriate function or type.
Application scenario of the recursive method: When multiple parameters of different types and quantities have similar actions, the recursive method is suitable.
There were several examples in the previous sections on the use of recursion, so I won’t go into them here.
For the use of the entire parameter package pass, see the following code:
#include <iostream>
#include <string>
using namespace std;
class programmer
{
string name;
string sex;
int age;
string vocation;/ / career
double height;
public:
programmer(string name, string sex, int age, string vocation, double height)
:name(name), sex(sex), age(age), vocation(vocation), height(height)
{
cout << "call programmer" << endl;
}
void print(a)
{
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
cout << "age:" << age << endl;
cout << "vocation:" << vocation << endl;
cout << "height:"<< height << endl; }};template<typename T>
class xprintf
{
T * t;
public:
xprintf()
:t(nullptr)
{}
template<typename. Args>void alloc(Args ... args)
{
t = new T(args...) ; }void print(a)
{
t->print(a); }void afree(a)
{
if( t ! =nullptr )
{
delete t;
t = nullptr; }}};int main(a)
{
xprintf<programmer> xp;
xp.alloc("Xiao Ming"."Male".35."Programmer".169.5);
xp.print(a); xp.afree(a);return 0;
}
Copy the code
Xprintf is a generic interface. T is an unknown type in the class template. We do not know what type and how many parameters are required for its construction, so we can use the variable parameter template in its member function to pass the whole parameter package directly to the constructor. Which arguments are required depends on the type of argument to template type T.
5. The case of using template parameter packages in STL
Let’s go back to the original case, which is as follows:
template<typename. _Args>void emplace_front(_Args&&... __args);
Copy the code
This is a function inside the deque. Emplace_front is an optimized version of push_front. As you can see from its prototype, emplace_front is a typical use of a type template parameter package, but with the addition of am& and, as we discussed earlier, an rvalue reference. For rvalue references, if the element type is a primitive type such as int or double, there is not much difference between an rvalue reference and a direct pass.
So the parameter _Args&&… The elements in the container must be consistent. The arguments in the container are actually arguments to the element type constructor when the container is defined. The emplace_front function can be used to pass in an element’s construction argument directly. Here’s how to use it:
#include <deque>
#include <string>
#include <iostream>
class CMan
{
int age;
std::string sex;
double money;
public:
CMan(int age, std::string sex, double money)
:age(age), sex(sex), money(money)
{
std::cout << "call contrust" << std::endl;
}
CMan(CMan && other)
:age(other.age), sex(other.sex), money(other.money)
{
std::cout << "call move contrust"<< std::endl; }};int main(a)
{
std::deque<CMan> dq;
dq.emplace_front(30."man".12.3);
return 0;
}
Copy the code
Emplace_front (); CMan (); emplace_front (); emplace_front ();
#if __cplusplus >= 201103L
template<typename _Tp, typename _Alloc>
template<typename. _Args>#if __cplusplus > 201402L
typename deque<_Tp, _Alloc>::reference
#else
void
#endif
deque<_Tp, _Alloc>::
emplace_front(_Args&&... __args)
{
if (this->_M_impl._M_start._M_cur ! =this->_M_impl._M_start._M_first)
{
_Alloc_traits::construct(this->_M_impl,
this->_M_impl._M_start._M_cur - 1, std::forward<_Args>(__args)...) ; --this->_M_impl._M_start._M_cur;
}
else_M_push_front_aux(std::forward<_Args>(__args)...) ;#if __cplusplus > 201402L
return front(a);#endif
}
Copy the code
As you can see, STD ::forward is actually used to pass the entire parameter package into the memory allocator, which in turn passes the parameter package to the container’s element type constructor by calling operator new and STD :: Forward.
STD :: Forward means “perfect forward”, passing the parameters along as they are.
This is a practical use case for the second method of parameter package expansion we discussed in Section 4, except that STD ::forward is used to achieve perfect forwarding.
Well, this article is for you to introduce here, if you think the content is useful, remember to click a like oh ~