- 浏览: 261338 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
daknife:
谢谢你的这篇文章,让我大概了解了select的一部分底层原理。 ...
Linux-2.6.25 select系统调用源码分析 -
gjlzjb:
非常有用,谢谢哈。另外问下,您是否用过Pheonix Syst ...
Why Map/Reduce? -
zhangyafei_kimi:
canbo 写道请问,我怎么生成安装包,提供给其它用户安装呢? ...
下载最新的Google Chrome源码并编译 -
canbo:
请问,我怎么生成安装包,提供给其它用户安装呢?
下载最新的Google Chrome源码并编译
本系列全部转载自kuibyshev.bokee.com
1. 模板元编程的基本用途
1.1. 数值计算
上文提及的所有关于模板元编程的例子都是在编译时的数值计算,这是模板元编程的最简单直接的使用方法。数值计算主要利用模板的特化和局部特化能力进行递归演算,模板类被视为元函数,利用类中的一个静态常量保存结果。由于C++模板对非类型的参数有限制,一般只有整型和布尔型可以参加运算。元函数的结果一般放在一个静态的常量中,但对于整型而言还有一个更好的选择,可以放置在枚举中,其唯一的优点是静态常量是左值必须在运行期占有内存地址,也就是分配了内存空间,而枚举值不能作为左值,所以不占用内存空间,显得有些微的优势。这样,阶乘计算的例子可以改写如下:
#include <iostream>
template <unsigned n>
struct factorial
{
enum {
value = n * factorial<n-1>::value
};
};
template<>
struct factorial<0>
{
enum {
value = 1
};
};
int main()
{
std::cout<<factorial<6>::value<<std::endl; //6!=720
}
无论是编译期的递归还是运行期内存的节省,对比起模板元编程在数值计算上的不足,都显得有点得不偿失。主要有以下四点:
运算范围仅限于整型和布尔型,用途有限。
递归式的编程难于实行和理解。
C++编译器对编译期的递归都是有一定的限制的,C++标准建议进行17层递归实例化,这无疑不能满足稍复杂的程序。
大量消耗编译器资源的同时,降低了编译效率。
因此,用模板元编程作编译期数值计算并不在实践中经常使用,但它作为一个“中心设施”在MPL库中发挥着重要的作用[6]。
1.2. 解开循环(Loop Unrolling)
当计算两个向量进行点乘,而维数不定时,例如:
int a[]={1,3,5,7};
int b[]={2,4,6,8};
考虑下面计算点乘的代码:
template <class T>
inline T dot_product(int dim, T* a, T* b) {
T result(0);
for (int i=0; i<dim; i++) {
result+=a[i]*b[i];
}
return result;
}
这里的代码很平常,但对于性能要求极高并大量使用点乘的应用程序,也许想再节省一点开销。如果能减少循环的计数,对性能也有比较可观的提升。这样代码应该展开以直接计算:
T result=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];
但是我们希望泛化这个表达式,以便应用于不同维数的向量计算,这里,模板元编程正好可以发挥出它编译时计算和生成代码的能力。我们可以把代码改写成:
template <int DIM, class T>
struct DotProduct {
static T result(T* a, T* b) {
return *a * *b + DotProduct<DIM-1, T>::result(a+1, b+1);
}
};
//局部特化,用于结束递归
template <class T>
struct DotProduct<1, T> {
static T result(T* a, T* b) {
return *a * *b;
}
};
//包装函数
template <int DIM, class T>
inline T doc_product(T* a, T* b) {
return DotProduct<DIM, T>::result(a, b);
}
这种方法是定义了一个类模板DotProduct作为元函数,通过递归调用不断展开表达式,还定义了一个局部特化的版本,使它在维数递减到1时能够终结递归。
我们还留意到一个习惯,元函数都用struct而不是用class定义,这是因为struct中的成员可见性默认为public,在编写元函数时可以省却public:这个声明。
注意包装函数的接口已经改变,利用普通方法的函数是这样使用的:
doc_product(4, a, b);
现在的写法是:
doc_product<4>(a, b);
为什么不能使用相同的接口呢?原因是模板参数必须在编译时确定,所以DIM必须是一个常量,而不可能是一个变量。所以这是对此种编程技术的一个重大限制。当然,对于这类计算而言,向量的维数一般都能在编译时确定。
Todd Veldhuizen在1995年第一次提出了这项技术,并且把这种技术运用到高性能数学计算的Blitz++库中。此外,在Blitz++库中还大量运用到一种称为“表达式模板(Expression Template)”的技术,同样是为了减少线性代数计算中的循环次数和临时对象的开销。表达式模板尽管不属于模板元编程的范畴(因为它不是依赖编译时计算进行优化的),但它与模板元编程具有异曲同工的妙用,特别在高性能数学计算中能够发挥极大的用途。Todd Veldhuizen指出,通过这一系列的优化手段,C++在科学计算上的性能已经达到甚至超过Fortran的性能。
1.3. 类型处理
对类型的处理是模板元编程最重要和最具有现实意义的应用。由于模板可以接受类型参数,也可以通过typedef或定义内嵌类建立模板类的成员类型,再加以强大的模板特化的能力,使得类型计算几乎能有着数值计算所有的全部能力。
1.3.1. 类型分支选择
利用模板局部特化的能力,编译时的类型选择可以很容易做到:
//默认值,如果C为true就把第二个类型作为返回值
template<
bool C
, typename T1
, typename T2
>
struct if
{
typedef T1 type;
};
//局部特化,如果C为false就把第二个类型作为返回值
template<
typename T1
, typename T2
>
struct if<false,T1,T2>
{
typedef T2 type;
};
不过,有某些旧式编译器并不支持模板局部特化,这种情况下增加一层包装就可以巧妙地转为使用全局特化。
template< bool C >
struct if_impl
{
template< typename T1, typename T2 > struct result
{
typedef T1 type;
};
};
template<>
struct if_impl<false>
{
template< typename T1, typename T2 > struct result
{
typedef T2 type;
};
};
template<
bool C
, typename T1
, typename T2
>
struct if
{
typedef typename if_impl< C >
::template result<T1,T2>::type type;
};
元函数if是模板元编程中最简单但运用得最多的基础设施。
1.3.2. 类型的数据结构
把类型作为普通数据一样管理,这初看起来有点匪夷所思:普通数据可以运用struct或者array来组织,但C++并没有为类型提供一个专用的数据结构,可以利用的唯一设施是模板的参数列表。比如我们可以定义一个类型的“数组”如下
template <class a, class b, class c, class d, class e>
struct type_array;
但是为了使它真正像数组一样使用,还需要在其中定义一系列的typedef,比如某一下标的类型的提取等,类似于:
typedef a type1;
typedef b type2;
……
在这里,数组长度也无法动态变化是一个重大的缺陷。当然,有时候数组仍然是有用的,MPL就提供了一个框架为任何自定义的类型数据结构提供支持,下文会有所提及。现在先介绍一种更自动的类型组织方法——Typelist。
(2)Typelist
上面提到过模板元编程是函数式的编程,参照其他一些函数式编程语言对数据的组织,很容易得到一些启发。比如在Scheme(一种LISP的变体)中,基本的数据结构是表,其他数据结构都必须用表的形式来表达。一个表可以这样定义:
(”A” (“B” () (“C” () () ) ) (“D” () () ) )
这个表可以表示出一个二叉搜索树:
A |
B |
C |
D |
通过简单的表的递归,就可以构造出各种数据结构。注意到C++模板的实现体也是一种类型,利用类型的递归,同样的风格也可以吸收到模板元编程中。
Typelist的定义是很简单的:
template <class T, class U>
struct Typelist
{
typedef T Head;
typedef U Tail;
};
另外我们需要定义一个特殊的标记:
struct Nulltype;
这个无定义的类不能产生对象,它的存在仅仅为了提供一个结束的标记。现在我们定义一个Typelist,并在这一节里面反复使用:
typedef Typelist<int,
Typelist<float,
Typelist<long, Nulltype> > >
typelist;
这样的结构比起“数组”有什么优点呢?由于它的结构是递归的,我们可以很容易写一个通用的元函数提取它某一个位置的类型,我们甚至可以插入、修改和删除元素,然后返回一个新的Typelist。
(3)提取Typelist中的类型
如果需要按照位置来提取Typelist类型,可以定义这样一个元函数:
//声明
template <class List, unsigned int i> struct typeat;
//局部特化,当处理到需要提取的位置时,Head就是要返回的类型
template <class Head, class Tail>
struct typeat<Typelist<Head, Tail>, 0>
{
typedef Head result;
};
//如果未到需要提取的位置,在下一个位置继续递归
template <class Head, class Tail, unsigned int i>
struct typeat< Typelist<Head, Tail>, i>
{
typedef typename typeat<Tail, i-1>::result result;
};
这里使用了局部特化,对于不能支持局部特化的编译器,可以类似上面的if元函数的处理手法,适当修改这里的代码。
这个元函数按照以下方式调用:
typedef typeat<typelist, 0>::result result;
如果试图定义一个变量:
result a=<chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="1.2" unitname="F">1.2f</chmetcnv>;
编译器会抱怨无法把一个float类型转换为result类型,因为上面定义的typelist的第一个类型是int。而把下标0改为1以后,则可以编译通过,这证明元函数可以在编译时正确选择出所需的类型。
(3) 修改Typelist中的元素
实例化以后的模板已经成为一种类型,所以是不可能进行修改的,要达到修改的效果,唯一的方法是返回一种新的类型,使它带有新的Typelist。
比如,如果要在一个已有的Typelist中添加一个类型,可以定义一个这样的元函数:
//声明
template <class List, class T> struct append;
//如遇到把空类型加入空类型,只需要返回 一个空类型
template <>
struct append<Nulltype, Nulltype>
{
typedef Nulltype result;
};
//如果把一个非空类型加入空类型,那么就可以直接返回
//一个只有一个元素的Typelist
template <class T> struct append <Nulltype, T>
{
typedef Typelist<T, Nulltype> result;
};
//如果把一个Typelist加入空类型,那么就可以
//直接返回这个Typelist
template <class Head, class Tail>
struct append <Nulltype, Typelist<Head, Tail> >
{
typedef Typelist<Head, Tail> result;
};
//当未到达Typelist尾部(遇到空类型)时,递归调用append元函数
template <class Head, class Tail, class T>
struct append <Typelist<Head, Tail>, T>
{
typedef Typelist<Head,
typename append < Tail, T>::result>
result;
};
这个append元函数不仅能插入一个类型,也可以把一个Typelist添加到另一个Typelist尾部。如果这样使用的话:
发表评论
-
The Elements of Programing Style
2009-08-09 18:26 1230把代码写清楚,别耍小聪明。 想干什么,讲的简单点、直接点。 只 ... -
6个变态的C语言Hello World程序
2009-06-01 09:37 807转载自:http://cocre.com/?p=914 下面 ... -
在VS2005中使用IBM Purify的注意事项
2009-05-12 12:24 3921Rational Purify 使用及分析实例可以见这里htt ... -
boost.pool源码整理和使用说明
2007-07-22 13:49 270Source #ifndef __KIMI_BOOST_PO ... -
一个STL风格的动态二维数组
2007-07-22 18:05 1490#ifndef __KIMI_BOOST_ARRAY2#def ... -
boost.any源码整理和使用说明
2007-08-24 22:44 1991Source #include <algorithm& ... -
boost.array源码整理和使用说明
2007-08-24 22:45 1215Source #include <cstddef> ... -
boost.BOOST_STATIC_ASSERT源码整理和使用说明
2007-08-24 22:49 1303Source #include <boost/conf ... -
boost.shared_ptr源码整理和使用说明
2007-08-24 22:51 4141Source #pragma once //share ... -
boost.lexical_cast源码整理和使用说明
2007-08-24 22:55 1473Source #include <cstddef> ... -
编译期判断类的继承性
2007-08-24 23:00 1063介绍一个雕虫小技:编译期判断类的继承性。具体来说就是类型U是否 ... -
boost.type_traits源码整理和使用说明(1)
2007-08-28 01:35 2023Introduction The Boost type-tr ... -
泛型快速排序
2007-08-28 03:20 899Source #ifndef kimi_quicksort ... -
C++ Meta Programming 和 Boost MPL(1)
2007-08-30 23:01 1551本系列全部转载自kuibyshev.bokee.com ... -
C++ Meta Programming 和 Boost MPL(2)
2007-08-30 23:02 1481本系列全部转载自kuibyshev.bokee.com ... -
C++ Meta Programming 和 Boost MPL(4)
2007-08-30 23:07 1630本系列全部转载自kuibyshev.bokee.com ... -
泛型归并排序
2007-09-18 00:23 1178#define SENTINEL_CARD (-1) # ... -
泛型插入排序
2007-09-18 00:25 1168#pragma once #include <iter ... -
boost.tuple源码整理和使用说明
2007-10-07 23:13 1533Introduction A tuple (or n-tup ... -
才发现VC中也可以检测内存泄漏
2009-03-30 14:54 1336#include <stdio.h> ...
相关推荐
C++ Metaprogramming 和 Boost MPL C++ 模板元编程的一些要点
2004年的英文文章,对Boost C++ 模板元编程的入门介绍,包括使用示例。
Boost库是一个经过千锤百炼、可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的发动机之一。 Boost库由C++标准委员会库工作组成员发起,其中有些内容有望成为下一代C++标准库内容。 Boost中比较有...
Boost程序库探秘 深度解析C++准标准库第2版(501-640)_part3
用C++可变模板参数实现的一个类型容器,该代码只在GCC4.4上编译过,其他编译器可以不支持,使用者需要GCC4.4以上编译器,可以到GCC官网上下载
Boost程序库探秘 深度解析C++准标准库(501-550) (第二版),只有我这里有全套的,赶紧来下载吧! (501-550)指页数
Boost库是一个经过千锤百炼、可移植、提供源代码的C++库,,作为标准库的后备,是C++标准化进程的发动机之一。 Boost库由C++标准委员会库工作组成员发起,其中有些内容有望成为下一代C++标准库内容。在C++社区中影响...
Boost 库涵盖的范围极广,有字符串和文本处理相关子库比如 format 库和 regexp 库,有容器相关子库比如 variant 库(和 Qt 的 QVariant 有得一拼),有迭代器子库比如 tokenizer 库(可以把字符进行 tokenize),...
MPU9150和MPU9250中MPL的用法,官方资料。
高清文字版,英文版,template、metatemplate学习。。
这是一个讲述C++模板高级应用的书籍,主要参考了《C++设计新思维》以及《产生式编程》两本书,同时也参考了BOOST的思想,对C++模板的应用自认为达到了非常的高度,其中也蕴含了非常先进的思想:“像编写程序一样来...
与 Boost.MPL 不同的是,这个包利用了 C++11 特性,从而产生了更小的代码库。 主要特点: 广泛的算法库,适用于可变参数模板和(可能是用户定义的)模板类型(如std::tuple )。 编译时容器,例如map 、 vector和...
python库。 资源全名:mpl_interactions-0.17.3-py3-none-any.whl
STM32平台的MPL3115A2气压传感器的应用.pdf
安裝方式參考首部曲: python 程式設計入門-金融商管實務案例第 2 章。2.設定環境變數假設 anaconda35 是安裝在 C:\Users\user\
MPU9250MPL官方版本 已移植好STM32F407例程寄存器版(本人也成功移植到STM32F103上需要的请另外联系),能直接接上MPU9250使用,运行稳定后yaw角不漂移。上传只是希望更多的人开发九轴MPL库,然后可以多多交流学习。
汉化 boost 文档,致力 boost 推广。 如果你对本项目有兴趣,欢迎加入,相关说明请查阅项目论坛: https://groups.google.com/ 到目前为止,各人贡献的译文如下: 贡献者 贡献的译文 alai04 accumulators, any, ...
主要介绍Freescale的MPL3115A2气压传感器的性能、内部寄存器设置、工作模式配置等,给出基于STM32的MPL3115A2气压测量系统的应用实例。
mpl C ++中的简单运动计划库 依存关系 编译器 这需要c ++ 17。 尽管g ++-8支持c ++ 17(在Ubuntu 18.04上是默认设置),但g ++-10是必需的,并且较早的版本似乎不起作用。 要检查gcc和g ++的版本,请执行以下操作:...