1.3. Why metaprogramming?
问一下人们为什么想这么做是有意义的;毕竟,即使像factorial metafunction这样的玩具程序也有些深奥;为了演示类型计算如何应用在工作中,我们再来看一个简单的例子:
下面的代码产生了一个数组,用来容纳另一个数组其元素所有可能的排列组合
// can't return an array in C++, so we need this wrapper
template< typename T >
struct wrapper
{
T x;
};
// return an array of the N! permutations of 'in'
template< typename T >
wrapper< typename permutation_holder<T>::type >
all_permutations(T const& in)
{
wrapper<typename permutation_holder<T>::type> result;
// copy the unpermutated array to the first result element
unsigned const N = sizeof(T) / sizeof(**result.x);
std::copy(&*in, &*in + N, result.x[0]);
// enumerate the permutations
unsigned const result_size = sizeof(result.x) / sizeof(T);
for (T* dst = result.x + 1; dst != result.x + result_size; ++dst)
{
T* src = dst - 1;
std::copy(*src, *src + N, *dst);
std::next_permutation(*dst, *dst + N);
}
return result;
}
factorial的运行期版本在上面的all_permutations中是无法使用的,因为在C++中数组大小必须在编译期计算;然而,另有一些替补方案,我们如何才能避免元编程,最终的结论又是什么呢?
1,我们可以写程序直接解释元数据
在我们的factorial的例子中,数组大小可以是运行期的变量,因而我们能够直接使用运行期的factorial函数,但是这意味着动态分配,而它通常是昂贵的;更进一步,可以改写YACC让它接受一个函数指针,这个指针指向的函数将返回待解析流中的tokens和包含文法描述的字符串;然而,对大多数程序来说,这种方法将导致不可接受的运行期代价:解析器或者是不得不接受不确定的文法,为每一次解析都摸索一遍文法,或者是不得不为每次输入的文法在运行期复制the substantia table-generation和优化已经存在的YACC的工作
2,我们可以用我们自己的计算来代替编译器编译期间的计算
毕竟,传递给all_permutations的数组的大小一直是编译期可知的,因此对用户也是可知的,我们可以要求用户显式的提供结果的类型:
template< typename Result, typename T >
Result all_permutations(T const& input);
这种方法的代价是很明显的:我们放弃了表达能力(通过要求用户显式的指定实现细节)和正确性(通过允许用户指定错误的结果类型);任何一个手工写过解析器表格的人将告诉你,这种方法的不切实际正是YACC存在的原因
在元数据可以以用户其余程序同样的语言来表达的语言,如C++中,表达能力得到了进一步的加强:用户可以直接调用元程序,不需要学习另外的语法,不需要打断自己正常的代码流
因此,元编程的动机来自于下面三个因素的联合:效率,表达能力,正确性;在传统的程序中,一直存在着一股压力,一边是表达能力和正确性,一边是效率,而在元编程世界中,我们挥舞着新的武器:我们能够将表达能力所需要的计算从运行期转移到编译期
1.4. Why a metaprogramming library?
有人可能还想问一下为什么我们需要一个泛型库:
• 质量
适用于通用目的的库的代码,往往也适用于用户的目的,对库的开发者来说,这是中心任务;一般来说,任何C++标准库的实现给出的容器和算法,都要比大量存在的特定工程的实现给出的容器和算法更加灵活,实现的更好,因为库的开发是以它本身为目的的,而不是作为其他应用附带的任务,
对任何给定功能的集中的实现,更容易应用优化和改进
• 复用
比任何库都提供的代码的复用更重要,一个设计良好的泛型库建立了一个concepts和idioms的framework,这些concepts和idioms建立了解决问题的可复用的思维的模型;类似于C++ STL给了我们iterator concepts和function object protocol,Boost MPL提供了type-iterators 和 metafunction class protocol;一个考虑周全的idioms的framework节省了元程序员考虑与手头工作不相关的实现细节的时间,而让他们集中精力在手头问题上
• 可移植性
一个优秀的库可以消除,掩盖丑陋的平台差异;理论上一个元程序库是完全泛型的,不需考虑这些问题,但实际上对模板的支持依然有大量的不一致,即使是标准化四年之后;这或许不应该感到惊奇:C++模板是语言最深远,最复杂的特性,极大的增强了C++元编程的能力
• 有趣
一遍遍的重复同样的idioms是单调乏味的;它使程序员疲劳,降低生产力;更进一步,当程序员感到厌倦时他们会以比慢慢写代码更大的代价写出晦涩的,充满虫子的代码;通常最有用的库是机敏的程序员从海量的重复中提取出的简单的模式;MPL通过消除大量样板代码的重复来帮助程序员减少厌倦;
像人们能够看到的,MPL的开发是由推动了其它库的开发的同样的,实际的,真实世界中的问题推动的;或许这是一种迹象,表明模板元编程已经完成了最后的准备,即将离开深奥的领域,进入每个日常程序员的口头话题