当前位置:编程学习 > C/C++ >>

C++模板元编程

1994年,C++标准委员会在圣迭哥举行的一次会议期间Erwin Unruh展示了一段可以产生质数的代码。这段代码的特别之处在于质数产生于编译期而非运行期,在编译器产生的一系列错误信息中间夹杂着从2到某个设定值之间的所有质数:
1 // Prime number computation by Erwin Unruh
2 template <int i> struct D { D(void*); operator int(); };
3
4 template <int p, int i> struct is_prime {
5     enum { prim = (p%i) && is_prime<(i > 2 ? p : 0), i -1> :: prim };
6 };
7
8 template < int i > struct Prime_print {
9     Prime_print<i-1> a;
10     enum { prim = is_prime<i, i-1>::prim };
11     void f() { D<i> d = prim; }
12 };
13
14 struct is_prime<0,0> { enum {prim=1}; };
15 struct is_prime<0,1> { enum {prim=1}; };
16 struct Prime_print<2> { enum {prim = 1}; void f() { D<2> d = prim; } };
17 #ifndef LAST
18 #define LAST 10
19 #endif
20 main () {
21     Prime_print<LAST> a;
22 }

类模板D只有一个参数为void*的构造器,而只有0才能被合法转换为void*。1994年,Erwin Unruh采用Metaware 编译器编译出错信息如下(以及其它一些信息,简短起见,它们被删除了):
23 | Type `enum{}´ can´t be converted to txpe `D<2>´ ("primes.cpp",L2/C25).
24 | Type `enum{}´ can´t be converted to txpe `D<3>´ ("primes.cpp",L2/C25).
25 | Type `enum{}´ can´t be converted to txpe `D<5>´ ("primes.cpp",L2/C25).
26 | Type `enum{}´ can´t be converted to txpe `D<7>´ ("primes.cpp",L2/C25).

如今,上面的代码已经不再是合法的C++程序了。以下是Erwin Unruh亲手给出的修订版,可以在今天符合标准的C++编译器上进行编译:
27 // Prime number computation by Erwin Unruh
28
29 template <int i> struct D { D(void*); operator int(); };
30
31 template <int p, int i> struct is_prime {
32     enum { prim = (p==2) || (p%i) && is_prime<(i>2?p:0), i-1> :: prim };
33 };
34
35 template <int i> struct Prime_print {
36 Prime_print<i-1> a;
37     enum { prim = is_prime<i, i-1>::prim };
38     void f() { D<i> d = prim ? 1 : 0; a.f();}
39 };
40
41 template<> struct is_prime<0,0> { enum {prim=1}; };
42 template<> struct is_prime<0,1> { enum {prim=1}; };
43
44 template<> struct Prime_print<1> {
45     enum {prim=0};
46     void f() { D<1> d = prim ? 1 : 0; };
47 };
48
49 #ifndef LAST
50 #define LAST 18
51 #endif
52
53 main() {
54     Prime_print<LAST> a;
55     a.f();
56 }

在GNU C++ (MinGW Special) 3.2中编译这段程序时,编译器将会给出如下出错信息(以及其它一些信息,简短起见,它们被删除了):
57 Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 17]'
58 Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 13]'
59 Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 11]'
60 Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 7]'
61 Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 5]'
62 Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 3]'
63 Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 2]'

这个例子展示了可以利用模板实例化机制于编译期执行一些计算。这种通过模板实例化而执行的特别的编译期计算技术即被称为模板元编程。

顺便说一句,因为编译器的出错信息并未被标准化,所以,如果你在Visual C++、Borland C++等编译器上看不到这么详细的出错信息,请不必讶异。

一个可以运行的模板元编程例子

模板元编程(Template Metaprogramming)更准确的含义应该是“编‘可以编程序的’程序”,而模板元程序(Template Metaprogram)则是“‘可以编程序的’程序”。也就是说,我们给出代码的产生规则,编译器在编译期解释这些规则并生成新代码来实现我们预期的功能。

Erwin Unruh的那段经典代码并没有执行,它只是以编译出错信息的方式输出中间计算结果。让我们来看一个可以运行的模板元编程例子 — 计算给定整数的指定次方:
64 // xy.h
65
66 //原始摸板
67 template<int Base, int Exponent>
68 class XY
69 {
70 public:
71     enum { result_ = Base * XY<Base, Exponent-1>::result_ };
72 };
73
74 //用于终结递归的局部特化版
75 template<int Base>
76 class XY<Base, 0>
77 {
78 public:
79     enum { result_ = 1 };
80 };

模板元编程技术之根本在于递归模板实例化。第一个模板实现了一般情况下的递归规则。当用一对整数<X, Y>来实例化模板时,模板XY<X, Y>需要计算其result_的值,将同一模板中针对<X, Y-1>实例化所得结果乘以X即可。第二个模板是一个局部特化版本,用于终结递归。

让我们看看使用此模板来计算5^4 (通过实例化XY<5, 4>)时发生了什么:
81 // xytest.cpp
82
83 #include <iostream>
84 #include "xy.h"
85
86 int main()
87 {
88     std::cout << "X^Y<5, 4>::result_ = " << XY<5, 4>::result_;
89 }

首先,编译器实例化XY<5, 4>,它的result_为5 * XY<5, 3>::result_,如此一来,又需要针对<5, 3>实例化同样的模板,后者又实例化XY<5, 2>…… 当实例化到XY<5, 0>的时候,result_的值被计算为1,至此递归结束。

递归模板实例化的深度和终结条件

可以想象,如果我们以非常大的Y值来实例化类模板XY,那肯定会占用大量的编译器资源甚至会迅速耗尽可用资源(在计算结果溢出之前),因此,在实践中我们应该有节制地使用模板元编程技术。

虽然 C++标准建议的最小实例化深度只有17层,然而大多数编译器都能够处理至少几十层,有些编译器允许实例化至数百层,更有一些可达数千层,直至资源耗尽。

假如我们拿掉XY模板局部特化版本,情况会如何?
90 // xy2.h
91
92 //原始摸板
93 template<int Base, int Exponent>
94 class XY
95 {
96 public:
97     enum { result_ = Base * XY<Base, Exponent-1>::result_ };
98 };

测试程序不变:
99 // xytest2.cpp
100
101 #include <iostream>
102 #include "xy2.h"
103
104 int main()
105 {
106     std::cout << "X^Y<5, 4>::result_ = " << XY<5, 4>::result_;
107 }

执行如下编译命令:

C:\>g++ -c xytest2.cpp

你将会看到递归实例化将一直进行下去,直到达到编译器的极限。

GNU C++ (MinGW Special) 3.2的默认实例化极限深度为500层,你也可以手工调整实例化深度:

C:\>g++ -ftemplate-depth-3400 -c xytest2.cpp

事实上,就本例而言,g++ 3.2允许的实例化极限深度还可以再大一些(我的测试结果是不超过3450层)。

因此,在使用模板元编程技术时,我们总是要给出原始模板的特化版(局部特化版或完全特化版或兼而有之),以作为递归模板实例化的终结准则。

利用模板元编程技术解开循环

模板元编程技术最早的实际应用之一是用于数值计算中的解循环。举个例子,对一个数组进行求和的常见方法是:
108 // sumarray.h
109
110 template <typename T>
111 inline T sum_array(int Dim, T* a)
112 {
113     T result = T();
114     for (int i = 0; i < Dim; ++i)
115     {
116         resu

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,