赞
踩
从c++11开始,模板可以接受一组数量可变的参数,这种技术称为变参模板。
下面一个例子,通过变参模板打印一组数量和类型都不确定的参数。
- #include <iostream>
- #include <string>
-
- void print(void)
- {
- std::cout<<"........................"<<std::endl;
- }
-
- template <typename T, typename ... Ts>
- void print(T arg1, Ts ... args)
- {
- std::cout<<arg1<<std::endl;
- print(args ...);
- }
-
- int main(int argc, char **argv)
- {
- print("hello", 7.5, 10, std::string("building"));
- }
看到上面这段代码,首先会产生两个疑问:
下面将通过解释这段代码的运行过程,来解答上面的问题。仔细观察上面这段代码,不难发现,print函数模板是一个递归函数模板。执行过程大体如下:
从整个过程来看,arg1的主要作用就是从args迭代取值,print(void)负责处理args为空的情况。那么不定义void print(void)是否可以呢?答案是否定的,不定义该函数,编译将会报错“No matching function for call to 'print'”。
此处还应该注意一个问题,print和c/c++的printf原理不一样:printf通过va_list实现变参,而print函数模板是为每种情况都生成了一个重载函数,如下:
上面的信息来自于xcode调试,当然,也可以通过objdump查看,也会得到相同的结果,编译器确实生成了多个print重载函数:
当然,上面的代码还可以写成下面的样子:
- template <typename T>
- void print(T arg)
- {
- std::cout<<arg<<std::endl;
- }
-
- template <typename T, typename ... Ts>
- void print(T arg1, Ts ... args)
- {
- print(arg1);
- print(args ...);
- }
如果代码中没有print(arg1),程序知会打印最后一个参数building,print只有迭代到最后一个参数时,才会找到合适的函数print(T arg)。
但一定要注意,下面的实现方式是错误的,无递归结束条件,无限迭代,直到耗尽堆栈空间:
- void print(void)
- {
- }
-
- template <typename ... Ts>
- void print(Ts ... args)
- {
- print(args ...);
- }
从c++17开始,c++引入了一种更为简洁灵活的编程方式——折叠表达式,下面是一个简单的例子:
- #include <cstdio>
-
- template <typename ...T>
- auto sum(T ... args)
- {
- return (... + args);
- }
-
- int main(int argc, char **argv)
- {
- int s = sum(1, 2, 3, 4, 5);
- printf("%d\n", s);
- }
几乎所有的二元运算符都可以用于折叠表达式,下面是一些其他运算符的例子:
- template <typename F, typename ...T>
- auto apply(F f, T ...args)
- {
- return (f(args), ...);
- }
-
- template <typename ...T>
- bool and_op(T ...args)
- {
- return (args && ...);
- }
迭代表达式,仅仅是围绕一个操作符简单地展开,例如连加。因此,对于三元操作符:?,很难用迭代表达式来实现。所以,想使用迭代表达式和:?求一个集合中的极值,是无法实现的。但是可以通过其他方式实现,下面便是一种实现方式:
- template <typename T>
- struct min_op final
- {
- public:
- min_op(T data) : is_first(true), min_data(data) {
-
- }
-
- T operator()(T rhs) {
- if (is_first) {
- is_first = false;
- min_data = rhs;
- return min_data;
- }
-
- min_data = min_data < rhs ? min_data : rhs;
- return min_data;
- }
-
- private:
- bool is_first;
- T min_data;
-
- };
-
- template <typename T, typename ...Ts>
- auto min(T arg, Ts ... args)
- {
- min_op<T> op(arg);
- return (op(args), ...);
- }
很明显,这种方式还不如直接使用for循环直接利索。
与之前的递归迭代方式相比,迭代表达式最大的优点是编译器没有为其生成过多的重载函数。迭代表达式与之前的优点:不使用vector,不会生成多个函数,缺点:解决元素较少的情况。
变参模板的优点:
但其并不是完美无缺的,:
函数参数包除了转发所有参数外,还可以做其他事,例如计算他们的值。
- template <typename ... Ts>
- void print_doubled(Ts ... args)
- {
- print((args + args) ...);
- }
-
- ...
- print_doubled(1, 2, 3, 4, 5, 6);
- ...
作为另外一个例子,下面的函数通过一组变参下标来访问第一个参数中相应的元素:
- template<typename T, typename ...IDS>
- void print_elems(T a, IDS ...ids)
- {
- print(a[ids]...);
- }
-
- ...
- std::vector<int> v{1, 2, 3, 4, 5, 6};
- print_elems(v, 1, 3, 5);
- ...
提到变参模板类,首先会想到std::tuple,该种技术使得不定义新类型的前提下,多值返回成为一种可能,提供了更加灵活的编程方式,例如:
- template <typename T>
- std::tuple<T, T, T, T> calc(T x, T y)
- {
- return std::make_tuple(x + y, x - y, x * y, x / y);
- }
-
- ...
- auto result = calc(10.0, 2.5);
- ...
变参基类从不定数的基类派生出一个新的类,主要目的是代码复用,比普通写法更加方便,派生类无需引入基类头文件,但需要注意多继承陷阱。下面是一个简单的例子:
- #include <cstdio>
-
- struct fly_animal
- {
- void fly(void) { printf("flying !\n"); }
- };
-
- struct swim_animal
- {
- void swim(void) { printf("swiming !\n"); }
- };
-
- struct run_animal
- {
- void run(void) { printf("running !\n"); }
- };
-
- struct fish
- {
- //...
- };
-
- struct bird
- {
- //...
- };
-
- struct mammal
- {
- //....
- };
-
-
- template <typename ...Bases>
- struct overloader : Bases...
- {
- //using Bases::operator()...;
- };
-
-
- int main(int argc, const char **argv)
- {
- using flyfish = overloader<fly_animal, swim_animal>;
- flyfish ff;
- ff.fly();
- ff.swim();
-
- using crocodile = overloader<run_animal, swim_animal>;
- crocodile ccdl;
- ccdl.run();
- ccdl.swim();
-
- using cat = overloader<mammal, run_animal>;
- cat ct;
- ct.run();
-
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。