赞
踩
可变参数模板是一个接受可变数目参数的模板函数或模板类,在模板参数列表中,typename…指出接下来的参数表示0个或多个类型的列表,一个类型名后面跟省略号表示0个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。
可变参数模板通常是递归的,第一步调用处理包中的第一个实参,然后用剩余实参调用自身, sizeof…运算符可以获得包中元素数量。
如果需要用参数包中的参数,则一定要将参数包展开。有两种展开参数包的方法:
(1)通过递归的模板函数来将参数包展开
(2)通过逗号表达式和初始化列表方式展开参数包
template <class... T>
void f(T... args)
{
cout<<sizeof...(args)<<endl; //打印变参的个数
}
f();//0
f(1,2);//2
f(1,2.5,"");//3
代码
namespace A { //T 理解成0到多个不同的类型,那对应的参数args也应该是多个不同类型的参数 //参数包中可以容纳0 到多个模板参数,而且模板参数可以为任意的类型 template<typename... T> void Func(T... args) //args称为参数包 { cout << sizeof...(args) << endl; //打印可变参的数量 cout << sizeof...(T) << endl; //参数类型 } template<typename T, typename... U> void Func2(const T& a, const U&...args) //注意引用类型符的位置 { cout << sizeof...(args) << endl; //打印可变参的数量 } } int main() { A::Func(); A::Func(10, 25); A::Func("a", "b", 25); A::Func2(10); A::Func2(10,"abc",25); return 0; }
注意:
- T 理解成0到多个不同的类型,那对应的参数args也应该是多个不同类型的参数。
- 参数包中可以容纳0 到多个模板参数,而且模板参数可以为任意的类型。
- 在具体函数形参中,&的位置,出现在类型名的后面。
#include<iostream> using namespace std; //递归终止函数 void print() { cout<<"empty"<<endl; } //参数包展开函数 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; }
通过type_traits来展开并打印参数包 没写呢
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);
std::tuple就是一个可变模板类,template <class… Types> class tuple;
可变参数模板类的参数包展开的方式:
namespace B { //通过递归继承方式展开参数包 template<typename... args> class MyClass {}; //主模板 template<> class MyClass<> { public: MyClass() { printf("不带参数的构造函数被执行:%p\n", this); } }; template<typename First, typename... Others> class MyClass<First, Others...> : private MyClass<Others...> //偏特化 { public: MyClass() : _first(0) { printf("构造函数被执行:%p\n", this); } MyClass(First p, Others...q) :_first(p), MyClass<Others...>(q...) { cout << "_first = " << _first << endl; } First _first; }; } int main() { //B::MyClass<> v; B::MyClass<int, float, double> b(12,13.5,23); //先执行不带参数的构造函数,在展开参数包 return 0; }
//整型序列的定义 template<int...> struct IndexSeq{}; //继承方式,开始展开参数包 template<int N, int... Indexs> struct MakeIndexes:MakeIndexes<N-1,N-1,Indexes...>{}; //模板特化,终止展开参数包的条件 template<int... Indexs> struct MakeIndexes<0,Indexes...> { typedef IndexSeq<Indexes...>type; }; int main() { using T = MakeIndexes<3>::type; cout<<typeid(T).name()<<endl; return 0; }
namespace B { //通过递归组合方式展开参数包 template<typename... args> class MyClass {}; //主模板 template<> class MyClass<> { public: MyClass() { printf("不带参数的构造函数被执行:%p\n", this); } }; template<typename First, typename... Others> class MyClass<First, Others...> { public: MyClass() : _first(0) { printf("构造函数被执行:%p\n", this); } MyClass(First p, Others...q) :_first(p), _o(q...) { cout << "_first = " << _first << endl; } First _first; MyClass<Others...> _o; //组合关系(复合关系) }; } int main() { //B::MyClass<> v; B::MyClass<int, float, double> b(12,13.5,23); //先执行不带参数的构造函数,在展开参数包 return 0; }
这种展开参数包的方式需要写类的特化版本。
实现思路: 计数器从0 开始,每处理一个参数,计数器就 +1 ,一直到把所有参数处理完,最后用模板偏特化,作为递归调用结束。
namespace B { //count用于统计, 从 0开始,maxCount表示参数数量 template<int count, int maxCount, typename... T> class Test { public: static void Func(const tuple<T...>& t) { cout << "value = " << get<count>(t) << endl; Test<count + 1, maxCount, T...>::Func(t); } }; //需要一个特化版本,用于结束递归调用 template<int maxCount, typename...T> class Test<maxCount, maxCount, T...> { public: static void Func(const tuple<T...>& t) { } }; template<typename... T> void Func(const tuple<T...> &t) //可变参数函数模板 { Test<0, sizeof...(T), T...>::Func(t); } } int main() { tuple<float, int, int> tuple(12.5f, 100, 52); //元组:一堆各种东西的组合 B::Func(tuple); return 0; }
模板参数就是模板的参数,我们一般指定为T类型,实际上可以使用任何的名字,例如指定一个Foo的模板参数:
temlate<typename Foo>
Foo calc(const Foo& a, const Foo& b)
{
return a+b;
}
而模板模板参数则是模板的参数又是一个模板,例如:
template<typename T, template<typename U> typename Container>
class XCls
{
private:
Container<T> c;
};
模板的第一个参数是T类型,第二个参数是一个Container,他是一个可以指定一个U类型的变量。
那么如何使用他呢?
template<typename T>
class test
{
private:
T t;
};
int main(void)
{
XCls<std::string, test> mylst1;
return 0;
}
我们可以定义一个模板类,然后将其如上方式传入就可以了。
但是如果传入一个容器呢?比如:list
XCls<string, list> mylst1;
如果编译就会报错。我们分析一波:
将string 和 list传入到类XCls中,然后就会定义一个list的c变量,这样看起来是可以的,因此我们使用list容器的时候就是list<一个类型>,但是这里为什么就不行呢?是因为list容器实质上是有第二参数的,虽然第二参数有默认的参数,正如我们平常使用的那样,只需要指定一个参数,但是在这里无法通过编译,因此,我们使用如下解决办法:
template<typename T>
using Lst = std::list<T, std::allocator<T>>;
XCls<std::string, Lst> mylst2;
// 编译时需要加上std=c++11
使用C++11的using关键字的新功能,来定义一个类型的别名,而且使用在模板的情况下,因此我们编译时要指定std=c++11
然后我们将list的别名Lst传入进入,就可以编译通过。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。