从c++11开始,模板可以接受一组数量可变的参数,这种技术称为变参模板。
下面一个例子,通过变参模板打印一组数量和类型都不确定的参数。
#include
#include
void print(void)
{
std::cout<<"........................"<
void print(T arg1, Ts ... args)
{
std::cout<
看到上面这段代码,首先会产生两个疑问:
下面将通过解释这段代码的运行过程,来解答上面的问题。仔细观察上面这段代码,不难发现,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
void print(T arg)
{
std::cout<
void print(T arg1, Ts ... args)
{
print(arg1);
print(args ...);
}
如果代码中没有print(arg1),程序知会打印最后一个参数building,print只有迭代到最后一个参数时,才会找到合适的函数print(T arg)。
但一定要注意,下面的实现方式是错误的,无递归结束条件,无限迭代,直到耗尽堆栈空间:
void print(void)
{
}
template
void print(Ts ... args)
{
print(args ...);
}
从c++17开始,c++引入了一种更为简洁灵活的编程方式——折叠表达式,下面是一个简单的例子:
#include
template
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
auto apply(F f, T ...args)
{
return (f(args), ...);
}
template
bool and_op(T ...args)
{
return (args && ...);
}
迭代表达式,仅仅是围绕一个操作符简单地展开,例如连加。因此,对于三元操作符:?,很难用迭代表达式来实现。所以,想使用迭代表达式和:?求一个集合中的极值,是无法实现的。但是可以通过其他方式实现,下面便是一种实现方式:
template
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
auto min(T arg, Ts ... args)
{
min_op op(arg);
return (op(args), ...);
}
很明显,这种方式还不如直接使用for循环直接利索。
与之前的递归迭代方式相比,迭代表达式最大的优点是编译器没有为其生成过多的重载函数。迭代表达式与之前的优点:不使用vector,不会生成多个函数,缺点:解决元素较少的情况。
变参模板的优点:
但其并不是完美无缺的,:
函数参数包除了转发所有参数外,还可以做其他事,例如计算他们的值。
template
void print_doubled(Ts ... args)
{
print((args + args) ...);
}
...
print_doubled(1, 2, 3, 4, 5, 6);
...
作为另外一个例子,下面的函数通过一组变参下标来访问第一个参数中相应的元素:
template
void print_elems(T a, IDS ...ids)
{
print(a[ids]...);
}
...
std::vector v{1, 2, 3, 4, 5, 6};
print_elems(v, 1, 3, 5);
...
提到变参模板类,首先会想到std::tuple,该种技术使得不定义新类型的前提下,多值返回成为一种可能,提供了更加灵活的编程方式,例如:
template
std::tuple 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
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
struct overloader : Bases...
{
//using Bases::operator()...;
};
int main(int argc, const char **argv)
{
using flyfish = overloader;
flyfish ff;
ff.fly();
ff.swim();
using crocodile = overloader;
crocodile ccdl;
ccdl.run();
ccdl.swim();
using cat = overloader;
cat ct;
ct.run();
return 0;
}