fbpx
维基百科

可变参数模板

可变参数模板模板编程时,模板参数(template parameter)的个数可变的情形。

已经支持可变参数模板的编程语言D语言C++(自C++11标准)。

C++11 编辑

声明 编辑

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

例如,STL的类模板tuple可以有任意个数的类型名(typename)作为它的模板形参(template parameter):

template<typename... Values> class tuple; 

如实例化为具有3个类型实参(type argument):

tuple<int, std::vector<int>, std::map<<std::string>, std::vector<int>>> some_instance_name; 

也可以有0个实参,如 tuple<> some_instance_name;也是可以的。

如果不希望可变参数模板有0个模板实参,可以如下声明:

template<typename First, typename... Rest> class tuple; 

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

template<typename... Params> void printf(const std::string &str_format, Params... parameters); 

用途 编辑

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

  • 省略号出现在形参名字左侧,声明了一个参数包(parameter pack)[1]。使用这个参数包,可以绑定0个或多个模板实参给这个模板参数包。参数包也可以用于非类型的模板参数。
  • 省略号出现在包含参数包的表达式的右侧,称作参数包展开(parameter pack expansion),是把这个参数包解开为一组实参,使得在省略号前的整个表达式使用每个被解开的实参完成求值,所有表达式求值结果被逗号分开。这种表达式必须是可接受任意个数的以逗号分开的子表达式。注意这里的逗号不是作为逗号运算符,而是用作:
    • 被逗号分隔开的一组函数调用实参列表;(该函数必须是可变参数函数,而不能是固定参数个数的函数)
    • 被逗号分隔开的一组初始化器列表(initializer list);
    • 被逗号分隔开的一组基类列表(base class list)与构造函数初始化列表(constructor's initialization list);
    • 被逗号分隔开的一组函数的可抛出的异常规范(exception specification)的声明列表。
    • Lambda表达式的被逗号分隔开的用一对方括号包围的捕获。
    • 类模板或者函数模板定义中,
      • 类模板实参如果也是类模板并需要模板参数包作为实参,如template<unsigned... I1, unsigned... I2>struct concat<seq<I1...>, seq<I2...>>
      • 类模板的基类如果也是类模板,则其模板实参列表是逗号分隔的一个表达式,如:template<unsigned... I1, unsigned... I2>struct concat: seq<I1..., (sizeof...(I1) + I2)...>

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

如果在一个参数包展开中同时出现了两个参数包的名字,则二者在一起同时展开,且应该具有相同长度。这可能出现在类模板带有一个参数包,它的嵌套的类模板或成员函数模板带有另一个参数包。

使用方法 编辑

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

void printf(const char *s) //已经没有额外的参数了,这里将要耗尽字符串s {  while (*s) {  if (*s == '%') {  if (*(s + 1) == '%') {  ++s;  }  else {  throw std::runtime_error("invalid format string: missing arguments");  }  }  std::cout << *s++;  } } template<typename T, typename... Args> void printf(const char *s, T value, Args... args) //处理一对: (格式指示符,值参数) {  while (*s) {  if (*s == '%') {  if (*(s + 1) == '%') {  ++s;  }  else {  std::cout << value;  printf(s + 1, args...); // call even when *s == 0 to detect extra arguments  return;  }  }  std::cout << *s++;  }  throw std::logic_error("extra arguments provided to printf"); } 

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

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

#include <iostream>  template<typename type> type print(type param) {  std::cout<<param<<' ';  return param; } template<typename... Args> inline void pass(Args&&...) {} template<typename... Args> inline void expand(Args&&... args) {  pass( print(args)... ); } int main() {  expand(42, "answer", true); } 

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

#include <iostream> template<typename T> void some_function(T value) {  std::cout<<value<<' '; } template<typename... Args> inline void expand(Args&&... args) {  const int size = sizeof...(args) + 2;  int arr[size]{1(some_function(args),1 )...2};  std::cout<<std::endl<<sizeof(arr)/sizeof(int); //也可以用sizeof...(Args)运算符 } int main() {  expand(42, "answer", true); } 

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

int func() {} // termination version template<typename Arg1, typename... Args> int func(const Arg1& arg1, const Args&... args) {  process( arg1 );  func(args...); // note: arg1 does not appear here! } 

如果args...包含至少一个实参,则将调用第二个版本的函数;如果参数包为空将调用第一个“终结”版的函数。

可变参数模板可用于异常规范(exception specification)、基类列表(base class list)、构造函数初始化列表(constructor's initialization list)。例如:

template <typename... BaseClasses> class ClassName : public BaseClasses... { public:  ClassName (BaseClasses&&... base_classes) : BaseClasses(base_classes)... {} }; 

这个例子中的解包算子将复制所有模板参数类型为ClassName的基类型。构造函数取每个基类的引用,并初始化每个基类。

对于函数模板,可变模板参数可以转发(forward)。当与右值引用结合使用,这允许完美转发(perfect forwarding):

template<typename TypeToConstruct> struct SharedPtrAllocator {  template<typename ...Args> std::shared_ptr<TypeToConstruct> construct_with_shared_ptr(Args&&... params) {  return std::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));  } }; 

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

模板参数包中实参的个数可以如下确定:

template<typename ...Args> struct SomeStruct {  static const int size = sizeof...(Args); }; 

例如SomeStruct<Type1, Type2>::size为2,SomeStruct<>::size为0。需要注意,sizeof...sizeof是两个不同的运算符。

Lambda捕获例子:

template<class ...Args> void f(Args... args) {  auto lm = [&, args...] { return g(args...); };  lm(); } 

编译器实现 编辑

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

 int arr[]{([&]{ std::cout << args << std::endl; }(), 1)...}; 

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

 int arr[]{(std::cout << args << std::endl, 1)...}; 

例子 编辑

下述代码实现了C++14引入的make_integer_sequence函数模板。它产生一个模板类,其模板参数为0,1,2,...,N。可用于生成或访问std::tuple

#include <iostream>  // using aliases for cleaner syntax  template<unsigned...> struct seq { using type = seq; }; template<class S1, class S2> struct concat; template<unsigned... I1, unsigned... I2> struct concat<seq<I1...>, seq<I2...>>  : seq<I1..., (sizeof...(I1) + I2)...> {}; template<unsigned N> struct make_integer_sequence : concat<typename make_integer_sequence<N / 2>::type, typename make_integer_sequence<N - N / 2>::type>::type {}; template<> struct make_integer_sequence<1> : seq<0> {}; int printItem(unsigned k) {  std::cout << k << ' ';  return 0; } template<unsigned... I1> void printTemplate(seq<I1...> a) {  int nn[] = { printItem(I1)... }; } int main() {  make_integer_sequence<10> a;  printTemplate(a); } 

输出为

0 1 2 3 4 5 6 7 8 9 

参见 编辑

更多文章关于可变参数结构而非模板:

参考文献 编辑

  1. ^ cppreference: Parameter pack(since C++11). [2018-12-01]. (原始内容于2020-11-11). 
  2. ^ Bug 41933 - [c++0x] lambdas and variadic templates don't work together. GCC bugzilla database. Free Software Foundation. [8 December 2013]. (原始内容于2019-12-06). 

外部链接 编辑

  • Working draft for the C++ language, January 16, 2012(页面存档备份,存于互联网档案馆
  • Variadic Templates in D language (页面存档备份,存于互联网档案馆

可变参数模板, 是模板编程时, 模板参数, template, parameter, 的个数可变的情形, 已经支持的编程语言有d语言与c, 自c, 11标准, 目录, 声明, 用途, 使用方法, 编译器实现, 例子, 参见, 参考文献, 外部链接c, 编辑声明, 编辑, 11之前, 模板, 类模板与函数模板, 在声明时必须有, 固定数量的模板参数, 11允许模板定义有任意类型任意数量的模板参数, 例如, stl的类模板tuple可以有任意个数的类型名, typename, 作为它的模板形参, template, p. 可变参数模板是模板编程时 模板参数 template parameter 的个数可变的情形 已经支持可变参数模板的编程语言有D语言与C 自C 11标准 目录 1 C 11 1 1 声明 1 2 用途 1 3 使用方法 1 4 编译器实现 2 例子 3 参见 4 参考文献 5 外部链接C 11 编辑声明 编辑 C 11之前 模板 类模板与函数模板 在声明时必须有 固定数量的模板参数 C 11允许模板定义有任意类型任意数量的模板参数 例如 STL的类模板tuple可以有任意个数的类型名 typename 作为它的模板形参 template parameter template lt typename Values gt class tuple 如实例化为具有3个类型实参 type argument tuple lt int std vector lt int gt std map lt lt std string gt std vector lt int gt gt gt some instance name 也可以有0个实参 如 tuple lt gt some instance name 也是可以的 如果不希望可变参数模板有0个模板实参 可以如下声明 template lt typename First typename Rest gt class tuple 可变参数模板也适用于函数模板 这不仅给可变参数函数 variadic functions 如printf 提供了类型安全的附加机制 add on 还允许类似printf的函数处理不平凡对象 例如 template lt typename Params gt void printf const std string amp str format Params parameters 用途 编辑 省略号 在可变参数模板中有两种用途 省略号出现在形参名字左侧 声明了一个参数包 parameter pack 1 使用这个参数包 可以绑定0个或多个模板实参给这个模板参数包 参数包也可以用于非类型的模板参数 省略号出现在包含参数包的表达式的右侧 称作参数包展开 parameter pack expansion 是把这个参数包解开为一组实参 使得在省略号前的整个表达式使用每个被解开的实参完成求值 所有表达式求值结果被逗号分开 这种表达式必须是可接受任意个数的以逗号分开的子表达式 注意这里的逗号不是作为逗号运算符 而是用作 被逗号分隔开的一组函数调用实参列表 该函数必须是可变参数函数 而不能是固定参数个数的函数 被逗号分隔开的一组初始化器列表 initializer list 被逗号分隔开的一组基类列表 base class list 与构造函数初始化列表 constructor s initialization list 被逗号分隔开的一组函数的可抛出的异常规范 exception specification 的声明列表 Lambda表达式的被逗号分隔开的用一对方括号包围的捕获 类模板或者函数模板定义中 类模板实参如果也是类模板并需要模板参数包作为实参 如template lt unsigned I1 unsigned I2 gt struct concat lt seq lt I1 gt seq lt I2 gt gt 类模板的基类如果也是类模板 则其模板实参列表是逗号分隔的一个表达式 如 template lt unsigned I1 unsigned I2 gt struct concat seq lt I1 sizeof I1 I2 gt 具体例子见下文 实际上 能够接受可变参数个数的参数包展开的场合 必须是能接受任意个数的逗号分隔开的表达式列表 这也就是上述几种场合 如果在一个参数包展开中同时出现了两个参数包的名字 则二者在一起同时展开 且应该具有相同长度 这可能出现在类模板带有一个参数包 它的嵌套的类模板或成员函数模板带有另一个参数包 使用方法 编辑 可变参数模板可递归使用 可变模板参数自身并不可直接用于函数或类的实现 例如 printf的C 11可变参数的替换版本实现 void printf const char s 已经没有额外的参数了 这里将要耗尽字符串s while s if s if s 1 s else throw std runtime error invalid format string missing arguments std cout lt lt s template lt typename T typename Args gt void printf const char s T value Args args 处理一对 格式指示符 值参数 while s if s if s 1 s else std cout lt lt value printf s 1 args call even when s 0 to detect extra arguments return std cout lt lt s throw std logic error extra arguments provided to printf 这是一个递归实现的模板函数 注意这个可变参数模板实现的printf调用自身或者在args 为空时调用基本实现版本 没有简单机制去在可变模板参数的每个单独值上迭代 几乎没有什么方式可以把参数包转为单独实参来使用 通常这靠函数重载 或者当函数可以每次捡出一个实参时用哑扩展标记 dumb expansion marker include lt iostream gt template lt typename type gt type print type param std cout lt lt param lt lt return param template lt typename Args gt inline void pass Args amp amp template lt typename Args gt inline void expand Args amp amp args pass print args int main expand 42 answer true 上例中的 pass 函数是必须的 因为参数包用逗号展开后只能作为被逗号分隔开的一组函数调用实参 而不是作为逗号运算符 从而 pass 函数所能接受的调用实参个数必须是可变的 也即 pass 函数必须是可变参数函数 print args 编译不能通过 此外 上述办法要求print的返回类型不能是void 且所有对print的调用在一个非确定的顺序 因为函数实参求值的顺序是不确定的 如果要避免这种不确定的顺序 可以用大括号封闭的初始化器列表 initializer list 这保证了严格的从左到右的求值顺序 为避免void返回类型带来的麻烦 使用逗号运算符使得每个扩展元素总是返回1 例如 include lt iostream gt template lt typename T gt void some function T value std cout lt lt value lt lt template lt typename Args gt inline void expand Args amp amp args const int size sizeof args 2 int arr size 1 some function args 1 2 std cout lt lt std endl lt lt sizeof arr sizeof int 也可以用sizeof Args 运算符 int main expand 42 answer true 另一种方法使用重载函数的递归的终结版 termination versions 函数 这更为通用 但要求更多努力写更多代码 一个函数要求某种类型的实参与参数包 另一个函数没有参数 如下例 int func termination version template lt typename Arg1 typename Args gt int func const Arg1 amp arg1 const Args amp args process arg1 func args note arg1 does not appear here 如果args 包含至少一个实参 则将调用第二个版本的函数 如果参数包为空将调用第一个 终结 版的函数 可变参数模板可用于异常规范 exception specification 基类列表 base class list 构造函数初始化列表 constructor s initialization list 例如 template lt typename BaseClasses gt class ClassName public BaseClasses public ClassName BaseClasses amp amp base classes BaseClasses base classes 这个例子中的解包算子将复制所有模板参数类型为ClassName的基类型 构造函数取每个基类的引用 并初始化每个基类 对于函数模板 可变模板参数可以转发 forward 当与右值引用结合使用 这允许完美转发 perfect forwarding template lt typename TypeToConstruct gt struct SharedPtrAllocator template lt typename Args gt std shared ptr lt TypeToConstruct gt construct with shared ptr Args amp amp params return std shared ptr lt TypeToConstruct gt new TypeToConstruct std forward lt Args gt params 上例中 实参列表被解包给TypeToConstruct的构造函数 std forward lt Args gt params 的句法是以适当的类型转发实参 解包算子将把转发语法应用于每个参数 模板参数包中实参的个数可以如下确定 template lt typename Args gt struct SomeStruct static const int size sizeof Args 例如SomeStruct lt Type1 Type2 gt size为2 SomeStruct lt gt size为0 需要注意 sizeof 与sizeof是两个不同的运算符 Lambda捕获例子 template lt class Args gt void f Args args auto lm amp args return g args lm 编译器实现 编辑 GCC尚不支持lambda表达式包含为展开的参数包 2 因此下述语句编译不通过 int arr amp std cout lt lt args lt lt std endl 1 Visual C 2013支持上述风格的语句 当然 这里的lambda函数不是必需的 通常的表达式即可 int arr std cout lt lt args lt lt std endl 1 例子 编辑下述代码实现了C 14引入的make integer sequence函数模板 它产生一个模板类 其模板参数为0 1 2 N 可用于生成或访问std tuple include lt iostream gt using aliases for cleaner syntax template lt unsigned gt struct seq using type seq template lt class S1 class S2 gt struct concat template lt unsigned I1 unsigned I2 gt struct concat lt seq lt I1 gt seq lt I2 gt gt seq lt I1 sizeof I1 I2 gt template lt unsigned N gt struct make integer sequence concat lt typename make integer sequence lt N 2 gt type typename make integer sequence lt N N 2 gt type gt type template lt gt struct make integer sequence lt 1 gt seq lt 0 gt int printItem unsigned k std cout lt lt k lt lt return 0 template lt unsigned I1 gt void printTemplate seq lt I1 gt a int nn printItem I1 int main make integer sequence lt 10 gt a printTemplate a 输出为 0 1 2 3 4 5 6 7 8 9参见 编辑更多文章关于可变参数结构而非模板 可变参数函数 可变参数宏参考文献 编辑 cppreference Parameter pack since C 11 2018 12 01 原始内容存档于2020 11 11 Bug 41933 c 0x lambdas and variadic templates don t work together GCC bugzilla database Free Software Foundation 8 December 2013 原始内容存档于2019 12 06 外部链接 编辑Working draft for the C language January 16 2012 页面存档备份 存于互联网档案馆 Variadic Templates in D language 页面存档备份 存于互联网档案馆 取自 https zh wikipedia org w index php title 可变参数模板 amp oldid 67230917, 维基百科,wiki,书籍,书籍,图书馆,

文章

,阅读,下载,免费,免费下载,mp3,视频,mp4,3gp, jpg,jpeg,gif,png,图片,音乐,歌曲,电影,书籍,游戏,游戏。