C++11可变参数模板

C++11在C++98的基础上增加了很多新特性,可变参数模板(Variadic Templates)就是其中之一,而且是非常重要的一个特性。用可变参数模板可以实现很多骚操作,很大程度增加了语言的灵活性。C++11提供的新容器tuple就是基于可变参数模板实现。

所谓可变参数模板,就是我们在写模板类或者模板函数时,申明的模板参数的个数是不确定的。在C++11之前,已经能够实现可变参数的函数,比如常用的格式化输出函数printf等,但是模板却一定是固定的个数。我们知道可变参数的函数能给函数带来很大的灵活性,而可变参数的模板更是为开发者开辟了新世界。

由于模板参数的数量未知,在模板函数或者类的实现中就不能按常规的方式一个个获取各个类型参数。那如何才能提取出所有的模板参数?这时候,递归又派上用场了。

以下以几个示例来说明如何使用可变参数模板,内容都是参考侯捷老师介绍C++11新特性的课程。

实现可变参数的打印函数

1
2
3
4
5
6
7
8
template<typename T>
void Print(const T& last) { std::cout << last << std::endl; }

template<typename T, typename... Types>
void Print(const T& first, const Types&... args) {
std::cout << first << std::endl;
Print(args...);
}

以上代码就是实现了可变参数的打印函数,每次调用Print时,将一包参数的第一个打印出来,然后再调用Print,将其余的参数作为入参传入。

在模板参数申明的地方,typename… 表示一包模板参数,在函数定义的地方,Types…表示一包类型,在函数体内,args…表示一包参数变量。最开始看的时候可能不太习惯这种写法,多看几次代码就熟悉这种写法了。

实现哈希函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename T>
inline void MyHashInner(size_t& seed, const T& val) {
seed ^= std::hash<T>()(val) + (seed << 6) + (seed >> 2);
}

template<typename T>
inline void MyHash(size_t& seed, const T& val) {
MyHashInner(seed, val);
}

template<typename T, typename... Types>
inline void MyHash(size_t& seed, const T& val, const Types&... args) {
MyHashInner(seed, val);
MyHash(seed, args...);
}

template<typename... Types>
inline size_t MyHash(const Types&... args) {
size_t seed = 0;
MyHash(seed, args...);
return seed;
}

以上代码实现了一个hash函数,能够传入多个参数,算出一个hash值。

这里有两个需要注意的问题:

  • 特化的版本一定要放在泛化的版本的前面。由于编译时编译器会选择最符合当前调用的版本,所以如果在当前位置之前没有特化的版本,就只能用泛化的版本。比如上面的代码,如果将最后一个MyHash放在最开始的位置,那么编译时会找不到其他的特化版本,每次递归调用MyHash时,都是调用自己,因为这个版本的参数可以接收任意参数,而且每次调用自己,都会在参数包里面增加一个size_t类型的数据,导致无限递归下去且参数无限长。其实这和我们平时写代码时,一定要将函数声明写到调用之前是一个道理。

  • std::hash无法应用于原生的字符串,比如“abcd”这样字面的常量,如果是字符串,只能应用于std::string这样的类型,因为std::hash无法提供类似char[N]这样的类型的特化版本。

Tuple实现

C++11提供了一个新的容器tuple,该容器能够存储任意数量不同类型的元素,比如这样:

1
std::tuple<int, char, double> mytuple{1,'a',3.3};

tuple的内部实现依赖于可变参数模板的强大功能,完整的实现比较复杂,可通过一个简易的实现描述其原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename... Types> class Tuple;
template<> class Tuple<> {};

template<typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
typedef Tuple<Tail...> inherited;
public:
Tuple() {}
Tuple(Head head, Tail... args) :
head_(head), inherited(args...) {}

Head GetHead() { return head_; }
inherited& GetTail() { return *this; }

private:
Head head_;
};

主要思想是通过可变参数模板完成tuple模板类的递归生成。

上述的实现是通过继承实现,同样的功能也能通过组合实现,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename... Types> class TTuple;
template<> class TTuple<> {};

template<typename Head, typename... Tail>
class TTuple<Head, Tail...> {
public:
TTuple() {};
TTuple(Head head, Tail... args) :
head_(head), tail_(args...) {}

Head GetHead() { return head_; }
TTuple<Tail...>& GetTail() { return tail_; }

private:
TTuple<Tail...> tail_;
Head head_;
};