型别到型别的映射
变量,常数,类型在编译器看来可以分为两类,后两者是同一类的,原因是前者无法在编译期得知其确切的值。
对于判断语句,作用是在运行期选择不同的分支来运行。
那么在编译期选择不同的分支来运行用什么语句来做到呢?运行嘛,最终也要落在函数身上,不管是面向过程,还是面向对象都是如此,于是问题就划归成为在编译期选择不同的函数来运行。
使用常数来选择不同的函数,我想通过函数指针数组来实现是一种方法,但实在是不美观,不便捷;使用类型来选择不同的函数又该如何呢?
回答这个问题需要清楚不同的函数是什么意思,即确定一个函数函数的方法什么:1.函数名,2.函数输入参数。此时很容易想到的用类型来选择不同的函数的一个方法了:使用同名函数的重载。这的确是个好办法,先不说弊端,陈述型别到型别的映射的时候会说明:
//以下不同的类型拥有两种构造函数(许多的新类型都是采用的一个参数的构造函数) //但是唯一的旧类型用了两个参数的构造函数 struct TypeOld { template<class T> TypeOld(T, int){std::cout << "TypeOld被构造" << std::endl;} }; struct TypeNew1 { template<class T> TypeNew1(T){std::cout << "TypeNew1被构造" << std::endl;} }; struct TypeNew2 { template<class T> TypeNew2(T){std::cout << "TypeNew2被构造" << std::endl;} };//还有很多新的类型,省略 //为新类型造就一个构造器 template<class In, class Out> Out* Creator(const In& i) { return new Out(i); } //旧类型也要使用同名的构造函数怎么办,下面这样做可以么 //不可以的,因为函数不支持偏特化 //error C2768: “Creator”: 非法使用显式模板参数 template<class In> TypeOld* Creator<In, TypeOld>(const In& i) { return new TypeOld(i, 0); }
函数不支持模板偏特化,但是有近似的功能,函数重载,只可惜输出参数是不能参与重载决议的,所以要想办法让输出参数也进入输入参数中,以下是很容易想到的做法:
//那该怎么办,函数只能重载,在这个上想办法 //要重载就只能让Out参与输入参数 template<class In> TypeOld* Creator(const In& i, TypeOld) { return new TypeOld(i, 0); } //的确是重载了,但是Out如果是个很大的类,岂不是太浪费了,并且让输入参数的个数发生了变化
如果TypeOld是个构造复杂的类,这样太亏了,这也就是之前提到的重载弊端,于是编译期间型别到型别的映射的需求呼之欲出:
//首先解决Out这个白构造的输入参数,如果将这个类映射成为另一个(构造很便宜的)类就好了: template<class T> struct Type2Type { //typedef T ValueType; }; template<> struct Type2Type<TypeOld> { //typedef TypeOld ValueType; };
然后利用这个映射过后的类来做重载操作:
template<class In, class Out> Out* Creator(const In& i, Type2Type<Out>) { return new Out(i); } template<class In> TypeOld* Creator(const In& i, Type2Type<TypeOld>) { return new TypeOld(i, 0); }
这个类没有数据元素,因此构造他是不花费什么代价的,太好了,那么怎么会到当初的那个只有一个输入参数的函数接口呢?重载来了:
template<class In, class Out>//将这个重量级类Out Out* CreatorNew(In i) { return Creator(i, Type2Type<Out>()/*映射成为轻量级的Type2Type<Out>*/); }
由于模板支持非类形参,所以常数也是可以映射为类型并利用重载在编译期做分支(不同的函数)的选择。
总结一下:
利用类型配合函数的重载可以在编译期实现条件分支语句,但是要牺牲输入参数的构造代价,恰好类型到类型的映射可以将这部分的代价降低到最小,如此的配合就完美了。