我的C++实践(7):模板元编程实战

    (1)计算整数的幂。

//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定义的枚举值

 

转载于:https://my.oschina.net/abcijkxyz/blog/722956

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值