变长模板

变参模板

变参模板实列

可以将模板参数定义成能够接受任意多个模板参数的情况。这一类模板被称为变参模板,可以通过调用下面代码中的 print()函数来打印一组数量和类型都不确定的参数:

1
2
3
4
5
6
7
void print() {}
template <typename T , typename ...Types>
void print(T firstArgs , Types... args)
{
std::cout<<firstArgs<<'\n';
print(args...);
}

当传入一个或多个参数时会调用模板函数,这里通过将第一个参数单独声明,就可以先打印第一个参数,然后再递归的调用print(),args被称为剩余参数,是一个函数参数包Types ...args,使用了模板参数包定义类型Types,为了结束递归,重载了不接受参数的非模板函数 print()

变参和非变参模板的重载

上述print还可以通过如下方法实现:

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
void print(T arg)
{
std::cout<<arg<<'\n';
}
template<typename T,typename ...Types>
void print(T firstArg , Types...args)
{
print(firstArg);
print(args...);
}

当两个函数模板的区别只在于尾部的参数包的时候,会优先选择没有尾部参数包的那一个函数模板

sizeof… 运算符

C++11 为变参模板引入了一种新的 sizeof 运算符:sizeof... 它会被扩展成参数包中所包含的参数数目,sizeof...既可以用于模板参数包也可以同于函数参数包,且二者效果相同(返回值相同),均返回当前参数包参数的个数

这样可能会让你觉得,可以不使用为了结束递归而重载的不接受参数的非模板函数 print(), 只要在没有参数的时候不去调用任何函数就可以了:

1
2
3
4
5
6
7
8
9
template<typename T, typename… Types>
void print (T firstArg, Types… args)
{
std::cout << firstArg << ’\n’;
if (sizeof…(args) > 0)
{ //error if sizeof…(args)==0
print(args…); // and no print() for no arguments declared
}
}

这种方法是错误的,,因为通常函数模板中 if 语句的两个分支都会被实例化。是否使用被实例化出来的代码是在运行期间(run-time)决定的,而是否实例化代码是在编译期间 (compile-time)决定的。因此如果在只有一个参数的时候调用 print()函数模板,虽然args...为空,if 语句中的 print(args...)也依然会被实例化,但此时没有定义不接受参数的 print()函数, 因此会报错

变参下标

下面的函数通过一组变参下标来访问第一个参数中相应的元素:

1
2
3
4
5
6
7
8
9
10
11
template <typename C ,typename ...Idx>
void printElems(C const & coll , Idx... idx)
{
print(coll[idx]...);
}

int main()
{
std::vector<std::string> coll = {"good","times","say"} ;
printElems(coll,2,0,1); //相当于 print(coll[2],coll[0],coll[1])
}

也可以将非类型模板参数声明成参数包

1
2
3
4
5
6
7
template<std::size_t… Idx, typenam>
void printIdx (C const& coll)
{
print(coll[Idx]…);
}
std::vector<std::string> coll = {"good", "times", "say", "bye"};
printIdx<2,0,3>(coll);