可变参数模板是一个接受可变数目参数的模板函数或模板类,在模板参数列表中,typename…指出接下来的参数表示0个或多个类型的列表,一个类型名后面跟省略号表示0个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。
可变参数模板通常是递归的,第一步调用处理包中的第一个实参,然后用剩余实参调用自身, sizeof…运算符可以获得包中元素数量。
一、可变参数模板函数
如果需要用参数包中的参数,则一定要将参数包展开。有两种展开参数包的方法:
(1)通过递归的模板函数来将参数包展开
(2)通过逗号表达式和初始化列表方式展开参数包
1、可变参数模板函数的定义
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 到多个模板参数,而且模板参数可以为任意的类型。
- 在具体函数形参中,&的位置,出现在类型名的后面。
2、递归函数方式展开参数包
- 提供一个参数包展开的函数和一个递归终止函数。
- 重载的递归终止函数。
#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来展开并打印参数包 没写呢
3、逗号表达式和初始化列表方式展开参数包
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;
可变参数模板类的参数包展开的方式:
- 通过模板特化
- 通过继承方式
1、递归继承方式展开参数包
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;
}
2、继承方式展开参数包
//整型序列的定义
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;
}
3、递归组合方式展开参数包
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;
}
4、tuple和递归调用展开参数包
这种展开参数包的方式需要写类的特化版本。
实现思路: 计数器从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传入进入,就可以编译通过。
四、可变参数模板消除重复代码
- 泛型的打印函数
- 工厂函数