c++模板元编程[metaprogram]
by micolai josuttis, david vandevoorde
摘自c++ templates: the complete guide一书
[译者注:翻译本文,全为引介一种(相对于译者的孤陋而言)全新的编程方法。版权所有于原著者,笔者不敢稍假借之。
原文笔误甚多,族繁不可计数。笔者水平有限,改之恐失信于原著,不改恐遗害于读者。对行文中的一些明显错漏,皆按自己的理解作了补正,并列原文于侧,以备查考。而文中的程序代码,未能一一核对,如有错误,还望谅解。
原文术语,笔者自行揣摩翻译,恐失其当,多在首次出现处加译注,以方括号佐之,如:元编程[metaprogramming];译注或行文补遗之处,皆循此例。盖凡方括号中之文字,皆笔者之言也;圆括号中者为原文自有。
本文摘自《c++ templates: the complete guide》一书第17章,文中所指章节数及页数皆指原书而言。感兴趣的读者可以自行查阅原书内容。
]
17.1 元编程[metaprogram]的第一个例子
“元编程”指的是“编‘编程序的’程序”。换言之,我们只给出代码的布局,而编程系统则在运行时生成代码来实现我们所希望的功能。通常,“元编程”这个词意味着一种“加诸自身”的特性——元编程的组件最终会变成它的产品代码/程序中的一部分。
元编程有何吸引人之处?和其他大部分编程方法一样,元编程的目标也是为了以更少的努力换取尽可能丰富的功能——这里的“努力”同样可以用代码长度、维护成本,或者其他标准来衡量。而元编程的独特之处在于,有些用户自定义的计算工作可以在翻译期发生。[使用元编程]潜在的动机要么是为了效率(通常翻译期计算得到的东西可以被优化掉),要么是为了简化接口(元程序[metaprogram]一般都会比展开后的最终程序短小),或者二者得兼。
元编程经常依赖于第15章发展出来的traits和type functions的概念。因此,我们建议你,在研读本章之前,你应该对此前的章节了然于胸。
1 元编程的第一个例子
在1994年的c++标准化委员会开会期间,erwin unruh发现可以用模板[templates]在编译期执行一些计算。他[用这种方法]写了一个生成质数的程序。这个小小练习中最令人目眩神迷的部分是:质数的生成是在编译过程中由编译器完成的,而不是在运行期。特别地,编译器还为从2到某个特定值之间的每一个质数都生成了一系列的错误信息[error message]。尽管这个程序的移植性不是特别的强(因为错误信息没有被标准化),但是这个程序的确展示了模板具现化[template instantiation]机制可以作为一种初级的递归语言,用以在编译期实现一些较为复杂的计算工作的能力。这种通过模板具现化在编译期执行计算的技术通常就被称为“模板元编程[template metaprogramming]”。
为了一窥元编程的全豹,我们从一个简单的练习开始(erwin的质数程序将在稍后的第318页展示给大家)。下面的程序展示了如何在编译期计算3的任意次方:
// meta/pow3.hpp
#ifndef pow3_hpp
#define pow3_hpp
//primary template to compute 3 to the n;
template<int n>
class pow3 {
public:
enum { result = 3 * pow3<n-1>::result };
};
//full specialization to end the recursion}
template<>
class pow3<0> {
public:
enum { result = 1 };
};
#endif // pow3_hpp
模板元编程背后的驱动力是模板的递归具现化[recursive template instantiation]。在我们的程序中,为了计算3^n,我们利用下面两条规则来驱动模板的递归具现化:
1. 3^n = 3 * 3^(n-1)
2. 3^0 = 1
第一个模板实现了通常情况下的递归规则:
template<int n>
class pow3 {
public:
enum { result = 3 * pow3<n-1>::result };
};
当用一个正整数n来具现化这个模板的时候,模板pow<3>必须首先计算它其中的枚举值result。这个枚举值又被定义成同一个模板被n-1具现之后的对应值。
第二个模板是一个特化版本,给出了递归的终点。它仅仅是给出了pow3<0>时的result值:
template<>
class pow3<0> {
public:
enum { result = 1 };
};
如果使用这个模板来计算3^7,只需具现化一个pow3<7>即可。现在让我们来研究一下,当我们这样具现化这个模板的时候,具体都发生了哪些事情:
#include <iostream>
#include "pow3b.hpp"
int main()
{
std::cout << "pow3<7>::result = " << pow3<7>::result
<< '\n';
}
首先,编译器具现化pow3<7>,它的result值是:
3 * pow3<6>::result [此处原pow3<5>疑为笔误,改之]
然后需要用6具现化同一个模板。依此类推,pow3<6>将具现化pow3<5>,后者又具现化pow3<4>……当具现化到pow3<0>的时候,result的值被定为1,至此递归结束。
pow3<>这个模板(包括其特化)就被称作“模板元程序[template metaprotram]”。该程序描述了一些运算,这些运算将在翻译期随着模板具现化的进行而被执行。这个例子相对比较简单,而且也看不出对我们有多大帮助,但是到了这个地步,[元编程]这个工具已经是唾手可得的了。
17.2 枚举值[enumeration values]vs静态常量[static constants]
在旧式c++编译器里,要想在类声明中使用“真正的常量”(所谓的“常量表达式”[constant-expression]),枚举值是唯一的选择。但是,自从c++标准化之后,情况已经有所改变。c++标准提出了所谓“类内静态常量性初始化”[in-class static constant initializer]的概念。下面这个简单的例子介绍了这一机制的结构:
struct trueconstants {
enum { three = 3 };
static int const four = 4;
};
在这个例子里,four也是一个“真正的常量”——一如three那样。
利用这个机制,我们的pow3元程序可以像下面这样实现:
// meta/pow3b.hpp
#ifndef pow3_hpp
#define pow3_hpp
//primary template to compute 3 to the nth
template<int n>
class pow3 {
public:
static int const result = 3 * pow3<n-1>::result;
};
// full specialization to end the recursion
template<>
class pow3<0> {
public:
static int const result = 1;