前言
C++的新特性--可变模版参数(variadic templates)是C++新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以它也是C++中最难理解和掌握的特性之一。虽然掌握可变模版参数有一定难度,但是它却是C++11中最有意思的一个特性,本文希望带领读者由浅入深地认识和掌握这一特性,同时也会通过一些实例来展示可变参数模版的一些用法。
变模版参数的展开
可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“...”。比如我们常常这样声明一个可变模版参数:template
- template <class... T>
- void f(T... args);
省略号的作用:
1.声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数; 2.在模板定义的右边,可以将参数包展开成一个一个独立的参数。
省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
可变模板参数分类:
1.可变模版参数函数
2.可变模版参数类
打印可变模版参数函数的参数个数
- #include <iostream>
- #include <string>
- using namespace std;
- template <class ...Type>
- void print(Type ...data)
- {
- cout << sizeof...(data) << endl;
- }
- int main()
- {
- print();
- print(1);
- print(1, "ILoveyou");
- print(1, 2, 3.4, "IMissyou");
- return 0;
- }
上面的例子中,print()没有传入参数,所以参数包为空,输出的size为0,后面两次调用分别传入两个和三个参数,故输出的size分别为2和3。由于可变模版参数的类型和个数是不固定的,所以我们可以传任意类型和个数的参数给函数print。这个例子只是简单的将可变模版参数的个数打印出来,如果我们需要将参数包中的每个参数打印出来的话就需要通过一些方法了。
展开可变模版参数函数的方法一般有两种:
1.通过递归函数来展开参数包。
2.逗号表达式来展开参数包。
递归方式展开参数包
通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的,如下面的例子:
- #include <iostream>
- using namespace std;
- //递归终止函数
- void print()
- {
- cout << "递归终止函数" << endl;
- }
- //展开函数
- template <class T,class ...Type>
- void print(T data,Type...exData)
- {
- cout << data << endl;
- print(exData...);
- }
- int main()
- {
- print(1, 2, 3, 4);
- return 0;
- }
上例会输出每一个参数,直到为空时输出"递归终止函数"。展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包exData...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。当然上述终止函数也可以写成带参数函数模板:
- template <class T>
- void print(T data)
- {
- cout<<data<endll
- }
接下来用模板函数作为终止函数写一个不限参求和函数,具体实现代码如下:
- #include <iostream>
- using namespace std;
- //递归终止函数
- template <typename Type>
- Type sum(Type t)
- {
- return t;
- }
- //展开函数
- template <class T,class ...Type>
- T sum(T a, Type ...b)
- {
- return a + sum<T>(b...);
- }
- int main()
- {
- cout << sum(1, 2, 3, 4) << endl;
- cout << sum(1, 2, 3) << endl;
- return 0;
- }
sum在展开参数包的过程中将各个参数相加求和,参数的展开方式和前面的打印参数包的方式是一样的。
逗号表达式展开参数包
递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须要一个重载的递归终止函数,即必须要有一个同名的终止函数来终止递归,这样可能会感觉稍有不便。有没有一种更简单的方式呢?其实还有一种方法可以不通过递归方式来展开参数包,这种方式需要借助逗号表达式和初始化列表。比如前面打印函数可以改成这样:
- #include <iostream>
- using namespace std;
- //递归终止函数
- template <class T>
- void print(T data)
- {
- cout << data << "\t";
- }
- template <class ...Type>
- void print(Type ...exData)
- {
- int array[] = { (print(exData),0)... };
- }
- int main()
- {
- print(1, 2, 3);
- cout << endl;
- print("张三", 1, 3);
- return 0;
- }
这个数组的目的纯粹是为了在数组构造的过程展开参数包。我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个递归终止函数了,具体代码如下:
C++中新特性之:initializer_list详解
C++11提供的新类型,定义在
- template< class T >
- class initializer_list;
下面稍微介绍一下initializer_list
一个initializer_list当出现在以下两种情况的被自动构造:
- 当初始化的时候使用的是大括号初始化,被自动构造。包括函数调用时和赋值
- 当涉及到for(initializer: list),list被自动构造成initializer_list对象
也就是说initializer_list对象只能用大括号{}初始化。拷贝一个initializer_list对象并不会拷贝里面的元素。其实只是引用而已。而且里面的元素全部都是const的。下面一个例子可以帮助我们更好地理解如何使用initializer_list:
- #include <iostream>
- #include <vector>
- #include <initializer_list>
- using namespace std;
- template <class T>
- struct S
- {
- vector<T> v;
- S(initializer_list<T> l) : v(l) {
- cout << "constructed with a " << l.size() << "-elements lists" << endl;
- }
- void append(std::initializer_list<T> l) {
- v.insert(v.end(), l.begin(), l.end());
- }
- pair<const T*, size_t> c_arr() const {
- return { &v[0], v.size() };
- }
- };
- template <typename T>
- void templated_fn(T arg) {
- for (auto a : arg)
- cout << a << " ";
- cout << endl;
- }
- int main()
- {
- S<int> s = { 1, 2, 3, 4, 5 };
- s.append({ 6, 7 , 8 });
- for (auto n : s.v)
- cout << ' ' << n;
- cout << endl;
- for (auto x : { -1, -2, 03 })
- cout << x << " ";
- cout << endl;
- auto al = { 10, 11, 12 };
- templated_fn<initializer_list<int> >({ 7, 8, 9 });
- templated_fn<vector<int>>({ 3, 5, 7 });
- return 0;
- }
可变模版参数类
std::tuple就是一个可变模板类
- template< class... Types >
- class tuple;
这个可变参数模板类可以携带任意类型任意个数的模板参数:
- tuple<int> tp1 = std::make_tuple(1);
- tuple<int, double> tp2 = std::make_tuple(1, 2.5);
- tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”);
可变参数模板的模板参数个数可以为0个,所以下面的定义也是也是合法的:
- tuple<> tp;
可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。
模版偏特化和递归方式来展开参数包
基本的可变参数模板类
- //前向声明
- template<typename... Args>
- struct Sum;
- //基本定义
- template<typename First, typename... Rest>
- struct Sum<First, Rest...>
- {
- enum { value = Sum<First>::value + Sum<Rest...>::value };
- };
- //递归终止
- template<typename Last>
- struct Sum<Last>
- {
- enum { value = sizeof (Last) };
- };
- int main()
- {
- cout << Sum<int, double, short>::value << endl;
- return 0;
- }
继承方式展开参数包
- //整型序列的定义
- template<int...>
- struct IndexSeq{};
- //继承方式,开始展开参数包
- template<int N, int... Indexes>
- struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...> {};
- // 模板特化,终止展开参数包的条件
- template<int... Indexes>
- struct MakeIndexes<0, Indexes...>
- {
- typedef IndexSeq<Indexes...> type;
- };
- int main()
- {
- using T = MakeIndexes<3>::type;
- cout <<typeid(T).name() << endl;
- return 0;
- }
可变参数模版消除重复代码
C++11之前如果要写一个泛化的工厂函数,这个工厂函数能接受任意类型的入参,并且参数个数要能满足大部分的应用需求的话,我们不得不定义很多重复的模版定义,比如下面的代码:
- #include <iostream>
- using namespace std;
- template<typename T, typename... Args>
- T* Instance(Args... args)
- {
- return new T(args...);
- }
- class A
- {
- public:
- A(int a) :a(a) {}
- A(int a, int b) :a(a) {}
- A(int a, int b,string c) :a(a) {}
- void print()
- {
- cout << a << endl;
- }
- int a;
- };
- class B
- {
- public:
- B(int a, int b) :a(a), b(b) {}
- void print()
- {
- cout << a << endl;
- cout << b << endl;
- }
- int a;
- int b;
- };
- int main()
- {
- A* pa = Instance<A>(1);
- B* pb = Instance<B>(1, 2);
- pa->print();
- pb->print();
- pa = Instance<A>(100, 2);
- pa->print();
- pa = Instance<A>(100, 2,"Loveyo");
- pa->print();
- return 0;
- }