我有一个问题......我不懂模板元编程.
问题是:我读了很多.但这对我来说没有多大意义:/
事实nr.1:模板元编程更快
templatestruct Factorial { enum { value = N * Factorial ::value }; }; template <> struct Factorial<0> { enum { value = 1 }; }; // Factorial<4>::value == 24 // Factorial<0>::value == 1 void foo() { int x = Factorial<4>::value; // == 24 int y = Factorial<0>::value; // == 1 }
所以这个Metaprogram更快......因为Constant Literal.
但是:在现实世界中我们有不变的文字吗?
我用的大多数程序都会对用户输入作出反
事实nr.2:模板元编程可以实现更好的可维护性.
是啊.析因示例可以维护......但是当涉及到复杂的函数时,我和大多数其他C++程序员都无法读取函数.
调试选项很差(或者至少我不知道如何调试).
模板元编程在哪里有意义?
正如因子不是非函数语言中递归的现实例子,它也不是模板元编程的现实例子.这只是人们想要向您展示递归的标准示例.
在为实际目的编写模板时,例如在日常库中,模板通常必须根据实例化的类型参数来调整它所做的事情.这可能变得非常复杂,因为模板有条件地有效地选择要生成的代码.这就是模板元编程的原因; 如果模板必须循环(通过递归)并在备选之间进行选择,它实际上就像在编译期间执行以生成正确代码的小程序.
这是一个来自boost文档页面的非常好的教程(实际上摘自一本精彩的书,非常值得一读).
http://www.boost.org/doc/libs/1_39_0/libs/mpl/doc/tutorial/representing-dimensions.html
我使用模板mete-programming为SSE调配操作符在编译期间优化shuffle.
SSE swizzles('shuffles')只能被屏蔽为字节文字(立即值),因此我们创建了一个'mask merger'模板类,它在编译时合并掩码,以便在发生多次shuffle时:
templatestruct _mask_merger { enum { ROW0 = ((target >> (((mask >> 0) & 3) << 1)) & 3) << 0, ROW1 = ((target >> (((mask >> 2) & 3) << 1)) & 3) << 2, ROW2 = ((target >> (((mask >> 4) & 3) << 1)) & 3) << 4, ROW3 = ((target >> (((mask >> 6) & 3) << 1)) & 3) << 6, MASK = ROW0 | ROW1 | ROW2 | ROW3, }; };
这可以工作并产生非凡的代码,而不会产生代码开销和额外的编译时间.
所以这个Metaprogram更快......因为Constant Literal.但是:在现实世界中我们有不变的文字吗?我使用的大多数程序都会对用户输入作出反应
这就是为什么它几乎没有用于价值观.通常,它用于类型.使用类型来计算和生成新类型.
有许多现实世界的用途,即使你没有意识到,其中一些你已经熟悉了.
我最喜欢的一个例子是迭代器.它们主要是用通用编程设计的,是的,但模板元编程特别适用于某个地方:
修补指针,以便它们可以用作迭代器.迭代器必须暴露一些typedef,例如value_type
.指针不这样做.
所以代码如下(基本上与你在Boost.Iterator中找到的相同)
templatestruct value_type { typedef typename T::value_type type; }; template struct value_type { typedef T type; };
是一个非常简单的模板元程序,但它非常有用.它允许您获取任何迭代器类型T的值类型,无论它是指针还是类,只需通过value_type
.
我认为上述在可维护性方面有一些非常明显的好处.只在迭代器上运行的算法必须实现一次.如果没有这个技巧,你必须为指针做一个实现,而另一个用于"适当的"基于类的迭代器.
类似的技巧boost::enable_if
也非常有价值.您有一个函数的重载,应该只为特定的类型集启用.您可以使用元编程来指定条件并将其传递给每个类型,而不是为每种类型定义重载enable_if
.
Earwicker已经提到了另一个很好的例子,一个表达物理单位和维度的框架.它允许您表达附加物理单位的计算,并强制执行结果类型.乘以米为单位可以产生数平方米.模板元编程可用于自动生成正确的类型.
但大多数情况下,模板元编程在小的,孤立的情况下使用(并且有用),基本上是为了平滑凸起和异常情况,使一组类型看起来和行为均匀,允许您更有效地使用通用编程
支持Alexandrescu的Modern C++ Design的建议.
当你正在编写一个可以在"选择Foo,Bar和Baz"方法中组合组合的库时,模板真的很闪耀,并且您希望用户以某种形式使用这些片段编译时间.例如,我合着了一个数据挖掘库,它使用模板元编程让程序员决定DecisionType
使用什么(分类,排序或回归),InputType
期望什么(浮点数,整数,枚举值,等等),以及KernelMethod
使用什么(它是一个数据挖掘的事情).然后,我们为每个类别实现了几个不同的类,以便有几十种可能的组合.
实现60个单独的类来执行此操作将涉及许多烦人的,难以维护的代码重复.模板元编程意味着我们可以将每个概念作为代码单元实现,并为程序员提供一个简单的接口,用于在编译时实例化这些概念的组合.
维度分析也是一个很好的例子,但其他人已经涵盖了这一点.
我曾经写过一些简单的编译时伪随机数生成器,只是为了弄乱人们的脑袋,但这并不能算上IMO.
阶乘的例子对于真实世界的TMP来说就像"Hello,world!"一样有用.用于通用编程:它是为了向您展示一些有用的技术(递归而不是迭代,"else-if-then"等),这是一个非常简单,相对容易理解的示例,与您的每一个都没有多大关系 - 编码.(你最后一次需要编写一个发出"Hello,world"的程序是什么时候?)
TMP是关于在编译时执行算法的,这意味着一些明显的优势:
由于这些算法失败意味着您的代码无法编译,失败的算法永远不会让您的客户,因此不会失败的客户.对我来说,在过去十年中,这是最重要的一个优势,这使我将TMP引入我所工作的公司的代码中.
由于执行模板元程序的结果是由编译器编译的普通代码,因此代码生成算法的所有优点(减少冗余等)都适用.
当然,由于它们是在编译时执行的,因此这些算法不需要任何运行时间,因此运行速度更快.TMP主要是关于编译时计算,其中包含一些(主要是小型的)内联函数,因此编译器有很多机会来优化生成的代码.
当然,也有缺点:
错误消息可能很糟糕.
没有调试.
代码通常很难阅读.
与往常一样,在每种情况下,您都必须权衡利弊.
至于一个更有用的示例:一旦掌握了类型列表和基于它们的基本编译时算法,您可能会理解以下内容:
typedef type_list_generator< signed char , signed short , signed int , signed long >::result_type signed_int_type_list; typedef type_list_find_if< signed_int_type_list , exact_size_predicate<8> >::result_type int8_t; typedef type_list_find_if< signed_int_type_list , exact_size_predicate<16> >::result_type int16_t; typedef type_list_find_if< signed_int_type_list , exact_size_predicate<32> >::result_type int32_t;
这是(几乎简化)我几周前写的实际代码.它将从类型列表中选择适当的类型,替换#ifdef
便携式代码中常见的orgies.它不需要维护,无需适应您的代码可能需要移植到的每个平台,如果当前平台没有正确的类型,则会发出编译错误.
另一个例子是:
template< typename TFunc, typename TFwdIter > typename func_traits::result_t callFunc(TFunc f, TFwdIter begin, TFwdIter end);
给定一个函数f
和一个字符串序列,这将剖析函数的签名,将序列中的字符串转换为正确的类型,并使用这些对象调用函数.它主要是TMP内部.