模板元编程
模板元编程只能处理在编译期的常量,不能处理运行期的变量。其所使用的语法也很受限,不能使用ifelse等语句,因此模板元编程需要很多的技巧,需要使用类型定义,枚举常量,集成,模板偏特化等来配合。
不能使用C++运行时的关键字(ifelse,for等)
常用的是:
- enum ,static const ,用来定义编译期整数
- typedef/using 用于定义元数据(类型等)
- T,Args… 用于声明元数据类型
- template 主要用于定义原函数
- :: 作用域运算符,用于解析类型作用域,获取计算结果
type_traits的使用(部分摘要)
- internal_constant
- conditional的妙用conditional
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value,long>::type
func(T t) {
return 11;
}
template<typename T>
typename std::enable_if<!std::is_arithmetic<T>::value,const char *>::type
func(T t) {
return "no arithmetic";
}
使用enable_if可以实现返回值不同的重载(参数相同,当然实际传递进去的参数可以是不同的类型,由编译器推导并生成不同的函数实例
可变模板参数的使用
【编译时获得数值序列最大值】
/*编译时获取序列最大值*/
template<size_t arg,size_t ...Rest>
struct Max_Ele;
template<size_t t>
struct Max_Ele<t> :public std::integral_constant<size_t, t> {
};
template<size_t arg1,size_t arg2,size_t ...Rest>
struct Max_Ele<arg1, arg2, Rest...> : std::integral_constant<size_t, (arg1>arg2) ?
Max_Ele<arg1, Rest...>::value : Max_Ele<arg2,Rest...>::value > {};
测试:cout << Max_Ele<2, 3, 43, 234, 13, 344>::value << endl;
/*
Max_Ele 本身是一个需要通过自身递归来生成类的实例,其基类模板实例依赖于子类Max_Ele
的模板参数;在生成具体的模板类实例时,将需要通过在继承的基类中,进行递归的展开,
从而在编译期获得结果,最终的结果也就是最终生成的类的实例(在这过程中,生成参数个数个
模板类的实例--直至最终的递归条件结束,生成了只有特定值的模板类实例,用其值与之前的
参数值进行比较。
*/
!应当注意在模板元编程中 ‘>’ 符号的使用,可能会闭合模板标签,而编译器无法告知错误正确的原因。在使用表达式语法时,尽量使用括号以示区分:如上arg1>arg2
【编译时获得最大对齐(align)值】
/*
编译期获得最大align 使用之前的Max_Ele,对模板参数包进行扩展
注意:此时的Max_Align自身无需递归,它的作用是提供可变模板参数交给Max_Ele去处理,
因此Max_Align作为子类不具有模板参数,其实例依赖于基类,可变模板参数是给基类的
在使用Max_Ele时,利用std::alignment_of 对模板参数进行扩展,获取对齐的值,
从而扩展成为数值序列,作为数值模板参数交由Max_Ele使用
*/
template<typename ...Args>
struct MaxAlign: std::integral_constant<int,
Max_Ele<std::alignment_of<Args>::value...>::value>{};
使用:cout << MaxAlign<char, int, long>::value << endl;
【编译期判断是否包含某种类型】
/*
编译期判断是否包含某种类型
*/
template<typename T,typename ...Cs>
struct Contains;
template<typename T,typename First,typename ...Rest>
struct Contains<T,First,Rest...>:std::conditional<std::is_same<T,First>::value,
std::true_type,Contains<T,Rest...>>::type{};
template<typename T>
struct Contains<T>:public std::false_type{};
使用:cout << std::boolalpha << Contains<int, char, long, int>::value << endl;
首先是类模板声明,至少包含一个模板参数–我们的目标参数;由于我们需要在编译期间遍历后续类型参数序列(这里是可变模板参数代替),所以我们需要使用递归的方式进行判断;递归终结,只有当前参数时,即没有匹配项,那么该类型即为false_type(public继承的含义即为是一个);然后是,在递归方法中,使用conditional作为编译时条件判断,如果发现匹配项,那么基类继承为真,如果不是(这里通过is_same判断类型是否相同),那么则进行递归下一步;注意继承的基类需要的是true_type 或者false_type :所以应该继承conditional< >::type ;这样才是争取的,而Contains本身与true_type属于同一种类型。
【编译期获得类型的索引】
/*
编译期获得类型的索引
*/
template<typename T,typename ...Args>
struct IndexOf;
template<typename T,typename First,typename ...Rest>
struct IndexOf<T,First,Rest...>: {
enum{value=IndexOf<T,Rest...>::value+1};
};
template<typename T,typename ...Rest>
struct IndexOf<T,T,Rest...> {
enum{value=0};
};
template<typename T>
struct IndexOf<T> {
enum{value=-100};/*此处见以下解释*/
};
使用:cout << IndexOf<int, char, float, int ,long, double>::value << endl;
- 首先解释下为何在最后一个特化模板类中给value赋值-100,因为按照这种思路获取索引值,如果没有获取到,那么返回的value会被加到上一步迭代结果的和中,这样的话我们将无法得到没有找到的结果,所以,这里使用-100,当然,如果你有更多的类型序列,那么可以将其设为更小的值,这样判断结果value是否小于0即可获取是否存在了,存在的话将返回所在序列的真正索引值;
- 然后分析整体设计方案,解决这个问题的核心思路是:首先使用类内定义enum变量来对索引值进行存储,这是一种方案,当需要进行统计时。针对这个问题,首先是给出一个普通情况,那就是前两个不同类型,这个时候往后进行迭代,判断下一个是否相同,当然每判断一个,value+1,然后给出一个偏特化的结果,当匹配到了,value为0,这样的话就可以得到累计的结果;当最终都没有匹配到,则按照上一段所说进行处理。至于是否有更好的解决方案,有待商榷.
【编译期根据索引获取类型】
/*
编译期根据索引获取类型
*/
template<size_t i,typename ...Args>
struct GetTypeByIndex;
template<size_t index,typename First,typename ...Args>
struct GetTypeByIndex<index, First, Args...> {
using type = typename GetTypeByIndex<index - 1, Args...>::type;
};
template<typename T,typename ...Args>
struct GetTypeByIndex<0, T, Args...> {
using type = T;
};
使用:GetTypeByIndex<3, int, long, char, double, float>::type test = 99;
比较简单,容易理解,不多解释。
【编译期遍历类型】
/*
编译期遍历类型
*/
template<typename Type>
void printTypeName() {
cout << typeid(Type).name() << "\t";
}
template<typename ...Args>
void printTypeNames() {
int a[]={(printTypeName<Args>(), 0)...};
}
核心思想很简单,第一个函数模板根据显示模板参数获取参数类型,使用typeid获取类型的type_info,打印其中的name信息。第二个函数模板,接收任意数量的参数类型(可变模板参数),可以使用任意的可以列表初始化的容器对模板参数进行展开,这里使用了逗号表达式,醉翁之意不在酒,做了两件事,扩展包,初始化了参数个数个0.
【模板元编程与tuple】
构造模板参数序列的实现
/*
构造模板参数数值序列<...,0,1,2,3....>
*/
// 序列模板定义
template<int ...Indexes>
struct SequenceIndexes{};
// 序列生成器
/*
核心思想:类模板实例化通过显示给出模板参数,这里为了通过输入一个参数来构建
参数序列,采用继承机制,来对参数数量和数值进行扩展,直到第一个参数被减为0
此时,0后面已经构成了0,1,2...Max-1的序列参数,转给SequenceIndexes即可
获得序列类型
*/
template<size_t Max,size_t ...List>
struct MakeSequenceIndexes:MakeSequenceIndexes<Max-1,Max-1,List...>{};
// 元编程递归构建之结束条件
template<size_t ...List>
struct MakeSequenceIndexes<0, List...> {
using type = SequenceIndexes<List...>;
};
尝试打印模板参数序列值(为了验证)
如果构造好了,编译器其实已经可以显示构造结果了,但这里我还是进行了手动验证
// 下面尝试打印构建的序列(是否有更好的方案,有待商榷
template<int T>
void print() {
cout << T << "\t";
}
template<int ...List>
void showSequenceList(SequenceIndexes<List...>) {
int a[] = { (print<List>(),0)... };
}
使用序列生成器与tuple进行转储与显示
/*
使用序列生成器与tuple转储实现可变模板参数值的打印
*/
// 打印可变模板参数变量值
// 打印函数递归终结条件
template<typename T>
void printArgs(T t) {
cout << t << "\t";
}
// 打印函数递归调用自身。。
template<typename T,typename ...Rest>
void printArgs(T t, Rest...rest) {
cout << t << "\t";
printArgs(rest...);
}
/*
使用get<>从tuple中取出每个参数值
get<>对SequenceIndexes的序列参数进行包展开
传入tuple,形参为任意参数的tuple的右值引用
调用前面的可打印任意参数的printArgs函数模板进行打印
*/
template<size_t ...Indexes, typename ...Args>
void takeFromTupleAndShow(SequenceIndexes<Indexes...>, std::tuple<Args...>&& tp) {
printArgs(std::get<Indexes>(tp)...);
}
/*
可以传入任意数量的任意类型的参数值,参数值将被保存至tuple中
通过sizeof...(Args)获得参数数量,以便生成索引值序列,使用MakeSequenceIndexes
生成。使用maketuple直接装入可变模板参数生成tuple(其内部将进行包扩展
*/
template<typename ...Args>
void storeArgsInTuple(Args...args) {
takeFromTupleAndShow(MakeSequenceIndexes<sizeof...(Args)>::type(), std::make_tuple(args...));
}
使用方法示例:
* showSequenceList(MakeSequenceIndexes<6>::type());
* printArgs(232, 123, “dsss”);
* storeArgsInTuple(12, 23, “dj”);