可变参数模板是什么

Q&A 2020-10-08 00:16:34 阅读(...)

可变参数模板是模板编程时,模板参数的个数可变的情形。已经支持可变参数模板的编程语言有D语言与C++。可变模板参数是C++ 11新增的最强大的特性之一,它对参数进行高度泛化,它能表示0到任意个数、任意类型的参数。

可变参数模板是模板编程时,模板参数(template parameter)的个数可变的情形。已经支持可变参数模板的编程语言有 D 语言与 C++(自 C++11 标准)。可变模板参数(variadic templates)是 C++ 11 新增的最强大的特性之一,它对参数进行高度泛化,它能表示 0 到任意个数、任意类型的参数。

可变参数模板是什么

C++11

声明

C++11 之前,模板(类模板与函数模板)在声明时必须有 固定数量的模板参数。C++11 允许模板定义有任意类型任意数量的模板参数。

上述模板类 tuple 可以有任意个数的类型名(typename)作为它的模板形参(template parameter)。例如,上述模板类可以实例化具有 3 个类型实参(type argument)为:

也可以有 0 个实参,如 tuple some_instance_name;也是可以的。如果不希望可变参数模板有 0 个模板实参,可以如下声明:

可变参数模板也适用于函数模板,这不仅给可变参数函数(variadic functions,如 printf)提供了类型安全的附加机制(add-on),还允许类似 printf 的函数处理不平凡对象。例如:

使用

省略号(…)在可变参数模板中有两种用途:

省略号出现形参名字左侧,声明了一个参数包(parameter pack)。使用这个参数包,可以绑定 0 个或多个模板实参给这个可变模板形参参数包。参数包也可以用于非类型的模板参数。

省略号出现包含参数包的表达式的右侧,则把这个参数包解开为一组实参,使得在省略号前的整个表达式使用每个被解开的实参完成求值,所有表达式求值结果被逗号分开。注意这里的逗号不是作为逗号运算符,而是用作:

被逗号分隔开的一组函数调用实参列表;(该函数必须是可变参数函数,而不能是固定参数个数的函数)

被逗号分隔开的一组初始化器列表(initializer list);

被逗号分隔开的一组基类列表(base class list)与构造函数初始化列表(constructor’s initialization list);

被逗号分隔开的一组函数的可抛出的异常规范(exception specification)的声明列表。

具体例子见下文。实际上,能够接受可变参数个数的参数包展开的场合,必须是能接受任意个数的逗号分隔开的表达式列表,这也就是上述四种场合。

可变参数模板经常递归使用。可变模板参数自身并不可直接用于函数或类的实现。例如,printf 的 C++11 可变参数的替换版本实现:

这是一个递归实现的模板函数。注意这个可变参数模板实现的 printf 调用自身或者在 args…为空时调用基本实现版本。

没有简单机制去在可变模板参数的每个单独值上迭代。几乎没有什么方式可以把参数包转为单独实参来使用。通常这靠函数重载,或者当函数可以每次捡出一个实参时用哑扩展标记(dumb expansion marker):

上例中的”pass”函数是必须的,因为参数包用逗号展开后只能作为被逗号分隔开的一组函数调用实参,而不是作为逗号运算符,从而”pass”函数所能接受的调用实参个数必须是可变的,也即”pass”函数必须是可变参数函数。print(args)…;编译不能通过。 此外,上述办法要求 print 的返回类型不能是 void;且所有对 print 的调用在一个非确定的顺序,因为函数实参求值的顺序是不确定的。如果要避免这种不确定的顺序,可以用大括号封闭的初始化器列表(initializer list),这保证了严格的从左到右的求值顺序。为避免 void 返回类型带来的麻烦,使用逗号运算符使得每个扩展元素总是返回 1。例如:

GCC 尚不支持 lambda 表达式包含为展开的参数包,因此下述语句编译不通过:

Visual C++ 2013 支持上述风格的语句。当然,这里的 lambda 函数不是必需的,通常的表达式即可:

另一种方法使用重载函数的递归的终结版(”termination versions”)函数。这更为通用,但要求更多努力写更多代码。一个函数要求某种类型的实参与参数包。另一个函数没有参数。如下例:

如果 args…包含至少一个实参,则将调用第二个版本的函数;如果参数包为空将调用第一个“终结”版的函数。可变参数模板可用于异常规范(exception specification)、基类列表(base class list)、构造函数初始化列表(constructor’s initialization list)。例如:

上例中,实参列表被解包给 TypeToConstruct 的构造函数。std::forward(params)的句法是以适当的类型转发实参。解包算子将把转发语法应用于每个参数。模板参数包中实参的个数可以如下确定:

例如 SomeStruct::size 为 2,SomeStruct::size 为 0。需要注意,sizeof…与 sizeof 是两个不同的运算符。

收藏 0个人收藏
走进科技生活方式

发表评论

登录后参与评论