上一篇:《深入理解C++11》笔记-常量表达式
本篇将介绍C++11中的变长模板,在这之前我们先回顾一下变长函数。
变长函数、变长宏
原来C++支持变长函数以及变长宏,能接收任何长度的参数列表:
// 变长函数
long sum(unsigned int count, ...)
{
va_list var;
long sum = 0;
va_start(var, count); // va_start读取变长参数列表
for (unsigned int i = 0; i < count; ++i)
{
sum += va_arg(var, int); // 按照从左到右的顺序取值
}
va_end(var); // 清空
return sum;
}
// 变长宏,用__VA_ARGS__取出变长值
#define LOG_OUT(fmt, ...) printf("file[%s]:line[%d], " fmt "", __FILE__, __LINE__, __VA_ARGS__)
int main(void)
{
LOG_OUT("out %d", sum(5, 1, 2, 3, 4, 5)); // file[xxx]:line[30], out 15
getchar();
return 0;
}
可以看到,变长函数能够接收任意个数的参数,但是实际上函数本身无法知道参数的类型,且无法限制传入参数的类型。因此,C++11中就有所谓的变长模板。
变长模板类
C++11中有一个tuple类模板,它的作用和pair类似,不过tuple支持变长参数而pair只支持两个参数。
std::pair, std::string> num_pair;
num_pair = std::make_pair(1, "one");
std::tuple, std::string, double> num_tuple;
num_tuple = std::make_tuple(1, "one", 1.0);
tuple的声明是template
,可以看到声明中有…省略符表示该模板的参数类型是变长的。Types被称为是”模板参数包”,这是一种新的模板参数类型。有了”模板参数包”,tuple就能接受任意多个参数作为模板参数。与普通模板参数类,模板参数包也可以是非类型的:
template <int... Args> class Example;
Example<1, 2, 3> ex;
一个模板参数包在模板推导时会被认为是模板的单个参数,所以为了使用模板参数包,我们需要将其解包。在C++11中,通常通过”包扩展”的表达式来完成。例如:
template class Type {};
template class Example : private Type...> {};
Example ex;
模板类型参数
先是被打包成了Types
,然后在调用Type模板通过Types ...
被还原成了
。但是这样只能解包两个类型参数的模板,怎么才能实现变长?tuple的实现方式给出了答案:
template<typename... Types> class tuple; // 变长模板声明
template<typename _This, typename... _Rest>
class tuple<_This, _Rest...>: private tuple<_Rest...> // 递归的偏特化
{
_This this_tpye;
};
template<> class tuple<> {}; // 空参数的偏特化
首先,声明了只包含一个模板参数包的tuple模板类。然后,又定义了一个双参数的偏特化tuple版本,一个是类型模板参数_This,另一个是模板参数包_Rest,同时将包扩展表达式的模板类tuple<_Rest...>
作为私有基类,_This作为第一个成员,这样就会引起基类的递归构造。最后,又定义了一个空参数的偏特化版本,作为递归的边界,当模板参数包参数为0个时结束递归。当我们实例化一个类型std::tuple
时,先构造出tuple
,然后是tuple
,最后完成构造tuple
。
这种变长模板的定义方式有些复杂,不过有效解决了模板参数变长的问题。同样的,该方式也能用于非类型模板类:
template <long... Args> class sum;
template<long _This, long... _Rest>
class sum<_This, _Rest...>
{
public:
static const long value = _This + sum<_Rest...>::value;
};
template<> class sum<>
{
public:
static const long value = 0;
};
int main(void)
{
std::cout << sum<1, 2, 3, 4, 5>::value << std::endl; // 15,在编译期计算
return 0;
}
变长模板函数
变长模板函数与变长模板类不同,变长模板函数参数需要声明”函数模板包”。例如:template
,其中Types是模板参数包,args则是这些参数类型对应的数据,即”函数参数包”,C++11规定函数参数包必须唯一且是函数的最后一个参数。使用这两个概念,就能实现变长函数的功能,且能指定类型:
// 空参数版本,终结模板的递归
void new_printf(const char* s)
{
while (*s)
{
if (*s == '%' && *++s != '%')
{
throw std::runtime_error("error");
}
else
{
std::cout << *s++;
}
}
}
// 参数递归
template<typename T, typename... Args>
void new_printf(const char* s, T value, Args... args)
{
while (*s)
{
if (*s == '%' && *++s != '%')
{
std::cout << value; // value是带类型的变量,和原来的变长函数不同
return new_printf(++s, args...);
}
else
{
std::cout << *s++;
}
}
throw std::runtime_error("error");
}
int main(void)
{
new_printf("test %d, %s\n", 1, "out"); // 可以使用默认类型
new_printf("test %c, %s\n", char(65), std::string("out")); // 可以指定参数类型
return 0;
}
变长模板-进阶
前面的内容可以看到,模板参数包能在模板类的基类描述中展开,也能在表达式中进行展开。下面列出所有可以展开模板参数包的情况:
另外,除了正常的包扩展表达式,还可以定义其他包扩展表达式:
// 正常的包扩展表达式
template class Example: private B {};
// 省略号在尖括号后面的包扩展表达式
template class Example: private B... {};
以上两种对于同样的实例化Example
,解包的方式是不同的:
// 正常的包扩展表达式为多参数
class Example: private B{};
// 省略号在尖括号后面的包扩展表达式为多继承
class Example: private B, private B{};
变长模板函数也有以上的特点,例如:
template
void wrapper(Types... types) // 用于包装my_print
{
}
template
Type my_print(Type t)
{
std::cout << t;
return t;
}
template
void func(Args... args)
{
wrapper(my_print(args)...);
}
int main(void)
{
func(1, ", ", 1.2); // my_print(args)...被解包为,my_print(1.2)、my_print(", ")、my_print(1),注意这里的顺序是反的(使用vs2015编译),书中的例子顺序是正的
getchar();
return 0;
}
C++11还提供了计算模板参数包中类型个数的方法sizeof…:
template <typename... Types> int args_count(Types... types)
{
return sizeof...(types); // 计算个数
}
int main(void)
{
std::cout << args_count(1, 1, 1) << std::endl; // 3
return 0;
}
变长模板还能作为模板的参数,但是使用起来比较复杂,后续有机会再进行介绍。
下一篇:《深入理解C++11》笔记-原子类型和原子操作