C++ 可变参数模板

发布时间:2023年12月18日

目录

参数包

包拓展?

参数包的展开

其它


参数包

在学习C语言时我们一定用过printf函数:

int printf ( const char * format, ... );

参数中的...表示的就是可变参数的意思,表示不确定传入多少个参数,用到几个就传几个。

也就是说,其实早在C语言阶段时期就已经有了可变参数的特性。而C++中又有模板的这一特性,所以C++11便新增了可变参数模板这一特性。而可变参数模板的一个核心关键内容就是参数包。

一个可变参数模板就是一个接受可变数目的模板函数或模板类。可变数目的参数被称为参数包。存在两种参数包:模板参数包,表示零个或多个模板参数;函数参数包,表示零个或多个函数参数。

我们用一个省略号来指出一个模板参数或者函数参数表示一个包。在一个模板参数列表中,class... 或t ypename... 指出接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。例如:

// Args是一个模板参数包;rest是一个函数参数包
// Args表示零个或多个模板类型参数
// rest表示零个或多个函数参数
template <typename T, typename... Args>
void foo (const T &t, const Args&... rest);

其中,可变参数模板依旧会根据参数内容生成不同的函数实体,例如对于上给定如下调用:

int i = 0; 
double d 3.14;
string s = "how now brown cow";

foo(1, s, 42, d);  // 包中有三个参数
foo(s, 42, "hi"); // 包中有两个参数
foo(d, s);  // 包中有一个参数
foo("hi");  // 空包

对应编译器会为foo实例化出四个不同的版本:

void foo (const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char[3]&);
void foo(const double&, const string&);
void foo(const char[3]&);

也就是说,可变参数模板并不要求类型一致,他会为我们生成对应类型的函数。

包拓展?

对于一个参数包,一般对它的操作就是包拓展,包拓展可以理解为就是将参数包展开。我们一般在参数包的后面跟三个点(...)表示包扩展,也就是展开这个参数包。例如

template<typename T, typename... args> // args是个参数包
// args...表示拓展(展开)args,就相当于args1、args2、args3……
void showArgs(T val, args... values) 
{
    cout << val << " ";
	showArgs(values...); // values...表示拓展(展开)values,这里就相当于是values1、values2……
}

特别的,C++还允许更复杂的包拓展模式,比如当一个集合中存在参数包时,那么在集合后跟一个包拓展符号(...)就表示对整个集合按照参数包的形式拓展(展开)。示例如下

template <class T>
void printArg(T val) {
	cout << val << " ";
}

template <class ...Args>
void fun(Args... args)
{
	// 如果集合中有参数包,那么后面的包扩展(...)就表示对整个集合进行参数包的展开
	// 例如下面代码的就相当于是:
    //   arr[] = { (printArg(args1), 0) , (printArg(args2), 0), (printArg(args3), 0) …… }
	int arr[] = { (printArg(args), 0)... };
	cout << endl;
}

参数包的展开

虽然参数包在理解上比较像一个数组,但参数包的展开却不能像数组一样展开。参数包一般是递归展开的,例如

// 基准情形
void showArgs()
{
	cout << endl;
}
// 参数包展开
template<typename T, typename... args>
void showArgs(T val, args... values)
{
	cout << val << " ";
	showArgs(values...);
}

示例说明如下:

可变参数模板的showArgs函数有两个参数,一个是单个参数val,一个是参数包values。可以看到,我们在递归传参时只传入了一个拓展的参数包values...。对此的解释为,当把整个参数包传过去时,val会自动匹配到values参数包的第一个参数,然后后面的第二个参数包参数就会拿到第二个之后的参数。就相当于每递归一次就剔除参数包的第一个元素。

可以看到,我们还写了一个非模板且没有参数的showArgs。这是因为递归到最后一定会出现参数包为空的情况,此时就需要一个没有参数的版本来控制结束,就相当于是递归过程中的终止递归的一个条件。

其它

随着可变参数模板的引入,也产生了一些伴生功能。

当我们需要直到参数包中有多少元素时,可以使用sizeof...运算符,其用法就等同于sizeof运算符。具体细节可以参考:sizeof... 运算符 (C++11 起) - cppreference.com

而之前我们在学习右值引用部分学到过完美转发,其中参数包在完美转发时是将参数包拓展符写在forward函数之后来表示参数包拓展(展开)的,例如

template <class...Args>
void showArgs(Args&&...args)
{
	__showArgs(std:forward<Args>(args)...);
}

其中,可变模板参数的右值引用并不会调用引用构造,因为参数是一层一层往下传的。

文章来源:https://blog.csdn.net/m0_73759312/article/details/135007510
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。