我的C++实践(7):模板元编程实战
2009-09-01 20:08
393 查看
(1)计算整数的幂。
Pow<N,M>用来计算N的M次幂。实例化层次是Pow<N,M>,Pow<N,M-1>,...,Pow<N,0>,共M+1层。我们始终要记住编译器对模板元编程中的实例化层次是有限制的(否则会层次太深的话会耗尽编译器的可用资源),我测试出gcc 4.2.4最多只允许31层的实例化,因此这里如果使用Pow<2,31>,Pow<5,40>等,则编译不会通过。
(2)求整数的平方根(向上取整)。可以用二分查找算法来查找,也可以用普通的从0开始迭代的查找算法。
在模板元编程中,我们要尽量少用条件运算符?:来执行路径选择,因为这会导致两个分支中的递归模板都会被实例化,产生数量庞大的实例化体。例如使用sqrt1.hpp中注释掉的那句,当实例化Sqrt<N,LO,HI>时,会导致Sqrt<N,LO,mid-1>和Sqrt<N,mid,HI>的完全实例,这样最终的实例化个数大约是N的2倍。我们应该用模板特化来执行路径选择,例如我们使用前面类型萃取技术中介绍的IfThenElse<bool,T1,T2>模板。在它的特化中ResultT只返回其中一个类型(如T1),注意把T1类型typedef成ResultT并不会导致被实例化。当最终查找结果ResultT::resut时,就会实例化ResultT,可见这只实例化了一个IfThenElse中的一个分支。最终的实例化个数趋向于lg(N)。另外,在二分查找的实现中,返回是其中的一个分支类型,最后返回时应该是mid=HI时的Sqrt<N,mid,HI>,因此必须提供一个mid=HI的特化来结束Sqrt模板的递归,否则还会再去实例化Sqrt<N,mid,HI>。而在迭代查找的实现中,当I*I>=N时模板递归结束,这时返回Value<I>分支,用Value<I>::result直接返回计算出的结果,递归结束。因此不需要提供Sqrt的特化来结束递归。
(3)计算向量的点乘。
(4)判断一个数是否是素数。
(5)打印连续的素数。
总结出模板元编程的计算完整性:
1)状态变量:也就是模板参数
2)迭代构造(相当于循环语句):通过递归模板
3)路径选择(相当于条件语句):通过使用特化或条件表达式?:
4)整型对象(相当于变量):用enum定义的枚举值
//pow.hpp:计算N的M次幂 #ifndef POW_HPP #define POW_HPP template<int N,int M> class Pow{ //计算N的M次方的基本模板 public: enum{ result=N*Pow<N,M-1>::result }; }; template<int N> class Pow<N,0>{ //用于结束递归的局部特化 public: enum{ result=1 }; }; #endif
Pow<N,M>用来计算N的M次幂。实例化层次是Pow<N,M>,Pow<N,M-1>,...,Pow<N,0>,共M+1层。我们始终要记住编译器对模板元编程中的实例化层次是有限制的(否则会层次太深的话会耗尽编译器的可用资源),我测试出gcc 4.2.4最多只允许31层的实例化,因此这里如果使用Pow<2,31>,Pow<5,40>等,则编译不会通过。
(2)求整数的平方根(向上取整)。可以用二分查找算法来查找,也可以用普通的从0开始迭代的查找算法。
//sqrt1.hpp:求整数的平方根(向上取整),用二分查找算法 #ifndef SQRT_HPP #define SQRT_HPP #include "ifthenelse.hpp" template<int N,int LO=0,int Hi=N> class Sqrt{ public: enum{ mid=(LO+HI+1)/2 }; //计算中点 //执行二分查找:用递归模板,找到结果的类型,然后返回结果 //enum{ result=(N<mid*mid)? Sqrt<N,LO,mid-1>::result : Sqrt<N,mid,HI>::result }; typedef typename IfThenElse<(N<mid*mid), Sqrt<N,LO,mid-1>, Sqrt<N,mid,HI> >::ResultT SubT; enum{ result=SubT::result }; //返回最终结果 }; template<int N,int M> class Sqrt<N,M,M>{ //终止递归的局部特化:适用于LO等于HI public: enum{ result=M }; }; #endif
//sqrt2.hpp:求整数的平方根(向上取整),用迭代查找算法 #ifndef SQRT_HPP #define SQRT_HPP #include "ifthenelse.hpp" template<int N> class Value{ //包装常量N的模板,使N变成一个类型Value<N> public: enum{ result=N }; //定义其要返回的结果reuslt }; template<int N,int I=0> class Sqrt{ //基本模板,从0开始迭代,以找到N的平方根(向上取整) public: //执行迭代来找到最终结果的类型:用递归模板 typedef typename IfThenElse<(I*I<N), Sqrt<N,N+1>, Value<I> >::ResultT SubT; enum{ result=SubT::result; } //返回最终找到的结果 }; #endif
在模板元编程中,我们要尽量少用条件运算符?:来执行路径选择,因为这会导致两个分支中的递归模板都会被实例化,产生数量庞大的实例化体。例如使用sqrt1.hpp中注释掉的那句,当实例化Sqrt<N,LO,HI>时,会导致Sqrt<N,LO,mid-1>和Sqrt<N,mid,HI>的完全实例,这样最终的实例化个数大约是N的2倍。我们应该用模板特化来执行路径选择,例如我们使用前面类型萃取技术中介绍的IfThenElse<bool,T1,T2>模板。在它的特化中ResultT只返回其中一个类型(如T1),注意把T1类型typedef成ResultT并不会导致被实例化。当最终查找结果ResultT::resut时,就会实例化ResultT,可见这只实例化了一个IfThenElse中的一个分支。最终的实例化个数趋向于lg(N)。另外,在二分查找的实现中,返回是其中的一个分支类型,最后返回时应该是mid=HI时的Sqrt<N,mid,HI>,因此必须提供一个mid=HI的特化来结束Sqrt模板的递归,否则还会再去实例化Sqrt<N,mid,HI>。而在迭代查找的实现中,当I*I>=N时模板递归结束,这时返回Value<I>分支,用Value<I>::result直接返回计算出的结果,递归结束。因此不需要提供Sqrt的特化来结束递归。
(3)计算向量的点乘。
//dotproduct.hpp:向量的点乘 #ifndef DOTPRODUCT_HPP #define DOTPRODUCT_HPP template<int DIM,typename T> class DotProduct{ //基本模板 public: static T result(T* a,T* b){ //第1个元素的乘积加到剩下的新向量的点乘 return *a * *b+DotProduct<DIM-1,T>::result(a+1,b+1); } }; template<typename T> class DotProduct<1,T>{ //作为结束条件的局部特化 public: static T result(T* a,T* b){ return *a * *b; //最后只有一个元素相乘 } }; //包装函数:为了使用方便 template<int DIM,typename T> inline T dot_product(T* a,T* b){ return DotProduct<DIM,T>::result(a,b); } #endif
//dotproducttest.cpp:向量点乘的测试 #include <iostream> #include "dotproduct.hpp" int main(){ int a[3]={1,2,3}; int b[3]={5,6,7}; std::cout<<"dot_product<3>(a,b) = "<<dot_product<3>(a,b)<<'/n'; std::cout<<"dot_product<3>(a,a) = "<<dot_product<3>(a,a)<<'/n'; return 0; }
(4)判断一个数是否是素数。
//isprime.hpp:判断一个数是否是素数 #ifndef IS_PRIME_HPP #define IS_PRIME_HPP template<int p,int i> class is_prime{ //基本模板:判断p是否是素数 public: enum{ prim=(p==2)||(p%i) && is_prime<(i>2?p:0),i-1>::prim }; }; template<> class is_prime<0,0>{ //用于结束模板递归的全局特化 public: enum{ prim=1 }; }; template<> class is_prime<0,1>{ //用于结束模板递归的全局特化 public: enum{ prim=1 }; }; #endif
(5)打印连续的素数。
//primeprinter.hpp:打印连续的素数 #ifndef PRIME_PRINTER_HPP #define PRIME_PRINTER_HPP #include <iostream> #include "isprime.hpp" template<int i> class PrimePrinter{ //打印i以下的连续素数 public: enum{ prim=is_prime<i,i-1>::prim }; static void print(){ if(prim) std::cout<<i<<std::endl; PrimePrinter<i-1>::print(); } }; template<> class PrimePrinter<1>{ //用于结束模板递归的全局特化 public: enum{ prim=0 }; static void print(){ return; } }; #endif
总结出模板元编程的计算完整性:
1)状态变量:也就是模板参数
2)迭代构造(相当于循环语句):通过递归模板
3)路径选择(相当于条件语句):通过使用特化或条件表达式?:
4)整型对象(相当于变量):用enum定义的枚举值
相关文章推荐
- 我的C++实践(7):模板元编程实战
- 我的C++实践(7):模板元编程实战
- C++工程师面试宝典系列之Linux编程之GCC编译工具实践
- 用例子学习 c++范型编程(模板元)
- C++-----编程实战(一)
- C++并发编程实战chapter1你好,C++的并发世界--笔记1--任务并行和数据并行
- win编程实践(3)【c++】-枚举值
- C++之:模板元编程(一)
- 模板元编程-C++
- C++后台开发 网络编程实践一
- 读书笔记 effective c++ Item 48 了解模板元编程
- C++之:模板元编程(二) 模板形参
- c++并发编程实战(2)
- C++简单模板元编程
- c++并发编程实战(1)
- C++中模板元编程原理及速度测试
- 模板元编程-C++
- C++ boost 组件简介:泛型编程与模板元编程
- 由 C++ 模板元编程看 F# 对链表的处理,兼谈 C 系语言和 FP 的优劣
- 从 C++ 模板元编程生产质数看 F# 函数式编程思想