可变模板参数

背景

在 C++11 之前,无论是类模板还是函数模板,都只能按其指定的样子,接受一组固定数量的模板参数;而C++11 加入了新的表示方法,允许任意个数、任意类别的模板参数,同时也不需要在定义时将参数的个数固定。

可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“...”

template<typename... Ts> 
void f(T... args);

上面的可变模版参数的定义当中,省略号的作用有两个:

  1. 声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数;
  2. 在模板定义的右边,可以将参数包展开成一个一个独立的参数。

我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

那么我们定义了变长的模板参数,如何对参数进行解包呢?

首先,我们可以使用 sizeof... 来计算参数的个数:

#include <iostream>

template<typename... Args>
void f(Args... args) {
	std::cout << sizeof...(args) << std::endl;
}

int main()
{
	f();        // 输出0
	f(1);       // 输出1
	f(1, "");   // 输出2

}

其次,如果我们需要将参数包中的每个参数打印出来的话怎么办呢?

那我们就需要对参数进行解包,到目前为止还没有一种简单的方法能够处理参数包,但有两种经典的处理手法:

递归模板函数

递归模板函数是一种标准的做法,但缺点显而易见的在于必须定义一个终止递归的函数
展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包Args...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。

下例会输出每一个参数,直到为空时输出empty。

#include <iostream>
using namespace std;

//递归终止函数
void f()
{
	cout << "empty" << endl;
}

//展开函数
template <class T, class ...Args>
void f(T head, Args... rest)
{
	cout << "parameter " << head << endl;
	f(rest...);
}


int main(void)
{
	f(1, 2, 3, 4);
	return 0;
}

递归调用的过程是这样的:

f(1,2,3,4);
f(2, 3, 4);
f(3, 4);
f(4);
f();

 上面的递归终止函数还可以写成这样:

#include <iostream>

template<typename T>
void f(T value) {
	std::cout << value << std::endl;
}

template<typename T, typename... Args>
void f(T value, Args... args) {        //该函数不接受0个参数
	std::cout << value << std::endl;
	f(args...);                        //迭代f(T value, Args... args)本身,当参数剩下一个将调用f(T value) 
}

int main() {
	f(1, 2, "123", 1.1);		//递归模板函数
	return 0;
}

修改递归终止函数后,上例中的调用过程是这样的

f(1, 2, 3, 4);
f(2, 3, 4);
f(3, 4);
f(4);

 借助逗号表达式和初始化列表

相关知识

逗号表达式:这种就地展开参数包的方式实现的关键是逗号表达式,逗号表达式会按顺序执行逗号前面的表达式。逗号表达式会按顺序执行逗号前面的表达式,比如:d = (a = b, a+c); 这个表达式会按顺序执行:b会先赋值给a,接着括号中的逗号表达式返回a+c的值,因此d将等于a+c。

初始化列表:通过初始化列表来初始化一个变长数组, { (func(args), 0)... }将会展开成( (func(arg1), 0), (func(arg2), 0), (func(arg3), 0), etc... ), 最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。

lambda表达式:定义[](){},声明并调用[](){}(). 

#include <iostream>
using namespace std;

int main()
{
	[](){                            //这里的()没有参数可以省略
		cout << "Hello,World\n";
	}();
}

实现代码:

#include <iostream>

template <class T>
void func(T t)
{
	std::cout << t << std::endl;
}

template <class ...Args>
void test(Args... args)
{
	int arr[] = { (func(args), 0)... };
}

int main() {
	test(1, 2, 3, 4);

	return 0;
}

其中的func并不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。

改进代码

我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个递归终止函数了,具体代码如下:

#include <iostream>

template<class F, class... Args>
void test(const F& f, Args&&...args)
{
	std::initializer_list<int>{(f(std::forward<Args>(args)), 0)...};
}

int main() {
	test([](int i) {std::cout << i << std::endl; }, 1, 2, 3);

	return 0;
}

通过初始化列表,(lambda 表达式, value)... 将会被展开。由于逗号表达式的出现,首先会执行前面的 lambda 表达式,完成参数的输出。唯一不美观的地方在于如果不使用 return 编译器会给出未使用的变量作为警告。

改进代码(C++14)

C++14的新特性泛型lambda表达式.更泛化的lambda表达式,适配auto.编译下面这个代码需要开启 -std=c++14

#include <iostream>

template<typename T, typename... Args>
auto print(T value, Args... args) {
	std::cout << value << std::endl;
	return std::initializer_list<T>{([&](){
		std::cout << args << std::endl;
	}(), value)...};
}
int main() {
	print(1, 2.1, "test");			//使用初始化列表
	return 0;
}

 

参考:https://www.cnblogs.com/qicosmos/p/4325949.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值