可变参数模板 Variadic Templates

最后更新于 2023-01-14 487 次阅读


C++ Primer Plus

CSDN : 贝勒里恩 http://t.csdn.cn/4rtqi

定义

可变参数模板可以创建可变参数类可变参数函数,用于接收任意数量的参数

要创建可变参数模板需要理解几个要点:

  • 模板参数包
  • 函数参数包
  • 展开参数包
  • 递归

模板和函数参数包

C++11提供了一个用省略号表示的元运算符,用于表示模板参数包的标识符。

模板参数包基本上是一个类型列表(Args)

函数参数包基本上是一个值列表(args)

Args和T的区别在于,T是一种类型匹配,而Args与任意数量包括0的类型匹配

函数参数包args包含的值列表与模板参数包Args包含的类型列表匹配,无论是类型还是数量

展开参数包

ANSI C原型的可变参数列表

ANSI C借鉴了C++中的原型,但这两种语言还有有区别的。其中最重要的区别是,为与基本C兼容,ANSI C中的原型是可选的,但在C++中,原型是必不可少的。例如,请看下面的函数声明:

void say_hi();

在C++中,括号为空与在括号中使用关键字void是等效的–意味着函数没有参数。在ANSI C中,括号为空意味着不指定参数–这意味着在后面定义参数列表。在C++中,不指定参数列表时应使用省略号:

void say_hi(…);

通常,仅当与接受可变参数的C函数(如printf())交互时才需要这样做。

我们非常熟悉的printf函数就是一种变长参数,简化版本的函数原型即为:int printf(const char * _Format,…);,其中,第一个参数即为必须制定的格式字符串,后面的省略号表示数量不定的参数列表。

使用上面提到的三个点表示的省略号即可达到定义一个变长参数的函数的目的,但是函数中如果获取这里面的所有参数呢?

这是要使用C语言中解决变长参数问题的若干宏定义va_start、va_arg、va_end,它们均定义在stdarg.h头文件中,以va开头(表示variable-argument可变参数),可根据预先定义的系统平台自动获取相应平台上各个数据类型的偏移量。它们的使用方法为:

va_list ap; //定义一个可变参数列表ap
va_start(ap,arg); //初始化ap指向参数arg的下一个参数
va_arg(ap,type); //获取当前参数内容并将ap指向下一个参数
va_end(ap); //释放ap 

实例如下

int find_sum(int num, ...)
{
	va_list ap;
	va_start(ap, num);
	int sum = 0;
	for (int i = 0; i < num; i++)
		sum += va_arg(ap, int);
	va_end(ap);

	return sum;
}

C++规则

为什么C++的做法不同?

按ANSI C的做法是有一定问题的,比如会造成无限递归:

定义函数

void func(1,"haha",9.6)

把1,"haha",9.6封装到args中得

void func(args...)

在函数内部调用func(args...),将会展开成func(1,"haha",9.6),也就是说它会不停的调用自己,造成无限递归

在可变参数模板中使用递归

核心理念为将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,以此类推直到列表为空。确保递归结束极其重要

template <typename T,typename... Args>
void func(T value,Args...args){
}

对于上述定义,函数的第一个实参决定了T和value的值,而其他实参决定了Args和args的值。然后递归调用func,并以args...的方式将其他实参传递给他。每次传递都会显示一个值,并传递缩短了的函数参数列表,直到列表为空为止。

Args和args的size

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

    std::cout << sizeof...(args) << std::endl;
}