模板元编程
来源:互联网 发布:安川伺服软件下载 编辑:程序博客网 时间:2024/06/07 07:15
《C++11/14高级编程:Boost程序库探秘》笔记
模板元编程简称元编程,本质上是泛型编程的一个子集,所以从广义上说,所有使用template的泛型编程都可以称作元程序——因为泛型代码并不是真正可编译执行的代码。
模板元编程的运行是在编译期,它把编译器变成了元程序的解释器。
语法元素
模板元编程产生的元程序是在编译期执行的程序,操作的对象也不是普通的变量,因此不能使用运行时的C++关键字(如if、else、for),可用的语法元素相当有限,最常用的几种:
- enum、static,用来定义编译期的整数常量
- typedef、using(C++11/14),最重要的元编程关键字,用来定义元数据
- template,模板元编程的“起点”,主要用于定义元函数
- “::”,域运算符,用于解析类型作用域获取计算结果(元数据)
元数据
元编程可操作的数据称为“元数据”(meta data),也就是C++编译器在编译期可操作的数据,它是模板元编程的基础。元数据都是不可变的,不能就地修改,最常见的是整数和C++的类型(type)。
元函数
元函数是模板元编程中用于操作处理元数据的“构件”,可以在编译期被“调用”,功能形式类似运行时的函数,但形式上却是一个模板类。
- 函数参数列表圆括号变成了模板列表声明的尖括号
- 函数的形参变成了模板参数(即元数据),并且要使用关键字typedname修饰
- 因为不能使用运行时关键字,所以元函数不能像普通函数那样使用return返回结果,而需要在元函数内部用typedef/using定义一个名为type的类型或名为value的值作为返回
- 元函数最后以分号结束
template<int N,int M>struct meta_func{ static const int value = N + M;};cout << meta_func<10, 10>::value << endl;int i = 10, j = 10;meta_func<i, j>::value; // error,i,j为运行时变量
元函数可以使用typedef/using关键字返回多个返回值,并且没有顺序关系,能够用”::”获取。
元函数转发
使用public继承,模板参数传递给父类完成元函数的“调用”,子类会自动获得父类的::type定义,同时也完成了元函数的返回。
struct select1st{ typedef T1 type;};template<typename T1,typename T2>struct forward :select1st<T2,T1> //默认public继承{};
等价于:
template<typename T1,typename T2>struct forward{ typedef typename select1st<T2, T1>::type type; //调用元函数计算};
mpl工具库
mpl是专门为模板元编程创建的工具库,提供了大量高质量高效的元编程工具,包括:
- 基本的数据类型:整数、pair、void等运行时类型的对应物
- 基本的数据运算:以元函数形式提供的算术运算、逻辑运算等运算功能
- 程序流程控制:以元函数形式提供的分支流程处理功能
- 容器:模仿STL风格的存储元数据(类型)的编译期容器
- 视图:容器的适配器,可以简化容器的操作
- 迭代器:模仿STL风格应用于容器的编译期迭代器
- 算法:模仿STL风格应用于容器和迭代器的编译期算法
- 高阶元数据:类似于函数对象的元编程构件
- bind/lambda表达式:编译期的lambda表达式,功能强大灵活
- 调试支持:提供了编译期的断言功能
- 辅助宏:各种配置宏和traits宏
mpl由头文件组成,所有组件位于名字空间boost::mpl,源代码在目录<boost/mpl>下,文件名和组件名一致,不存在统一的头文件,所以要手工添加包含的头文件。
整数类型
虽然整数本身就是元数据,在编译期可以计算,但直接操作整数对于元编程并不太方便,因为元编程更大的作用是类型计算,整数不是类型,为此,mpl库提供了数值包装器概念,把整数(int、long、bool等类型)包装为值元函数。
mpl库里的数值包装器元函数共有六个,分别包装了不同的整数类型(没有short):
char_<N> :包装char类型,值为Nint_<N> :包装int类型,值为Nlong_<N> :包装long类型,值为Nsize_t<N> :包装size_t类型,值为Nintergral_c<T,N> :包装类型为T的整数类型,值为Nbool_<N> :包装bool类型,值为N
这些元函数都有基本相同的形式,类摘要如下:
struct integral_wrapper //整数包装器{ BOOST_STATIC_CONSTANT(T, value = N); //包装整数值N typedef integral_c_tag tag; //类型标记 typedef T type; //返回自身 typedef T value_type; //整数类型 operator T() const { return this->value; } //转型操作 //bool没有下列两个元数据 typedef some_define next; //++N typedef some_define prior; //--N};
用法也基本相同,用::type返回自身,用::value返回被包含的整数值,对于非bool类型,提供了 next和prior两个内部类型定义,可以获得类似operator++和operator–效果,同时重载了转型操作,使得它们的实例可以在运行时隐式转换为被包装的整数。
#include <type_traits>#include <boost/mpl/int.hpp>#include <boost/mpl/integral_c.hpp>#include <boost/mpl/prior.hpp>using namespace boost::mpl;typedef int_<2> i2;typedef integral_c<short, 2> s2;int main(){ assert(i2::value == 2); assert(i2::value == s2::value); //is_same<T1,T2>::value比较T1和T2两个的类型是否一致 assert((std::is_same<i2::type, i2>::value)); assert((std::is_same<s2::value_type, short>::value)); assert(i2::next::value == 3); //内部成员获取递增值 assert(prior<s2>::type::value == 1); //使用元函数获取递减值 i2 two1; //声明两个包装器实例 s2 two2; //值均为常量2 int i = two1 + two2; //隐式类型转换为int参与运行时计算 assert(i == int_<4>()); //与int_元函数比较,使用()创建临时对象 return 0;}
mpl库提供了同C++内建的整数运算一样的整数类型运算:
- 递增递减运算:位于头文件<boost/mpl/next_prior.hpp>,包括next和prior两个元函数,不支持bool_。
- 算术运算:位于头文件<boost/mpl/arithmetic.hpp>,包括加减乘除、取模、取负值,支持使用多个参数,元函数有plus,minus,negate等。
- 比较运算:位于头文件<boost/mpl/comparison.hpp>,包括小于,大于,等于和不等于六种,元函数有less,greater,equal_to等。
- 位运算:位于头文件<boost/mpl/bitwise.hpp>,包括与、或、异或、移位等,元函数有bitand_、bitor_等。
- 逻辑运算:位于头文件<boost/mpl/logical.hpp>,包括与、或、非三种逻辑运算,元函数有and_、or_和not_。
- 其他运算:包括最大最小值min/max、整数的大小sizeof_等。
流程控制
mpl提供了四个元函数用于分支结构,类似于运行时的if-else语句
- if_和if_c
近似于 ?: 操作符
template<bool C,typename T1,typename T2>struct if_c{ typedef T1 type;};template<typename T1,typename T2>struct if_c<false, T1, T2> //if_c对false模板偏特化{ typedef T2 type;};template<typename C,typename T1, typename T2>struct if_{ typedef if_c<C::value, T1, T2> almost_type_t; typedef typename almost_type_t::type type;};
if_c和if_ 的条件不同,前者是bool类型,后者是一个有::value返回的值元函数(不一定是bool_)。
typedef if_c<true, int, long>::type mdata1;assert((is_same<mdata1, int>::value));typedef if_<boost::mpl::false_, float, double>::type mdata2;assert((is_same<mdata2, double>::value));
if_c 和if_ 不能有选择地忽略不需要计算的数据(缓式评估),必须计算所有的元数据,增加了计算时间。
- eval_if和eval_if_c
这两者的出现是为了解决上述if_c和if_ 的缺陷:缓式评估问题
template<bool C,typename F1,typename F2>struct eval_if_c : if_c<C,F1,F2>::type //元函数转发{};template<typename C,typename F1, typename F2>struct eval_if : if_<C,F1,F2>::type{};
使用时需要注意,后两个模板参数必须是元函数。
typedef eval_if_c<true, identity<int>, identity<long>>::type mdata1;assert((is_same<mdata1, int>::value));typedef eval_if<is_integral<mdata1>, identity<float>, identity<double>>::type mdata2;assert((is_same<mdata2, float>::value));
mpl容器
mpl容器与标准库的容器相似,但容纳的元素都是元数据(也就是类型),没有对元素的可拷贝可赋值要求。mpl容器没有成员函数,自身不能操纵容器内元素,只能通过外部元函数处理。
mpl序列容器包括以下内容:
- list:不同于std::list,它是一个单向链表,只能在序列的前端操作元素可以容纳无限个元素
- vector:跟std::vector一样有随机访问的功能,但它又有std::list的特性,可以在两端操作元素,容量有限,缺省最多能容纳20个元数据
- deque:类似std::deque,可以在两端操作元素。
mpl关联容器缺省最多能容纳20个元素,包含以下两种:
- set:类似std::multiset,是一个允许重复的元数据集合
- map:类似std::multimap,是一个允许重复的元数据映射关系集合
除了上述五个基本容器,mpl库还针对整数类型提供了一些特别的整数容器:
- range_c<T,N,M> :不可修改的包含[N,M]区间内整数的vector容器
- list_c<T,…> :包含类型为T的若干整数的list容器
- vector_c<T,…> :包含类型为T的若干整数的vector容器
- set_c<T,…> : 包含类型为T的若干整数的set容器
- string :专门存储char字符的容器,类似std::string,可以在编译期处理的字符串
判断一个类型是否是mpl容器可以用元函数is_sequence。
mpl容器的相关元函数
1) 容器容量操作。
- empty<C>
- size<C>
2)元素访问操作。
- front<C>
- back<C>
- at<C,k> :对于序列容器,返回第k个位置上的元素,对于关联容器,返回键为k的元素
- at_c<C,n> :仅用于序列容器, 返回第n个位置上的元素(n是long型整数)
3)迭代器操作
- begin<C>
- end<C>
4)元素变动操作
- push_front<C,t>
- push_back<C,t>
- pop_front<C>
- pop_back<C>
- clear<C>
- erase<C,pos>/erase<C,f,l>
- insert<C,pos,t>/insert<C,t>
容器是元数据,元数据是不可变的,所以变动操作都必须返回一个新的容器,原容器不会变化
5)关联容器特有操作
- has_key<C,k>
- erase_key<C,k>
mpl迭代器
mpl所有的迭代器都是只读,可分为三类:
- 前向迭代器:可以使用元函数next前进
- 双向迭代器:在前向迭代器的基础上增加使用元函数prior后退,即可逆向遍历
- 随机访问迭代器:在双向迭代器的基础上增加迭代器的距离运算
提供的迭代器元函数包括以下几个:
- deref<I> : 解引用迭代器,返回元数据
- next<I> :前进迭代器
- prior<I> :后退迭代器
- advance<I,n> :迭代器移动n个位置,对于双向迭代器n可以是负值
- distance<I1,I2> :返回两个迭代器之间的距离
- iterator_category<I> :获得迭代器的分类标志
mpl算法
1)插入器
- back_inserter<C> :在容器后端插入元素,要求支持push_back
- front_inserter<C> :在容器前端插入元素,要求支持push_front
- inserter<S,Op> :通用的插入器,以初始状态S开始执行Op操作完成插入动作。
typedef mpl::vector<char,int,long> vec; //容纳三个元素typedef copy<vec,mpl:back_inserter<vec>>::type vec2; //插入自己,相当于双倍assert((size<vec2>::value == 6));typedef mpl::string<> str1;typedef copy<mpl::string<'time'>, //临时元数据 mpl::front_inserter<str1>>::type str2;cout << c_str<str2>::value; //输出字符串emit
2)查询算法
- find<C,t> :查找元素t,返回迭代器
- contains<C,t> :值元函数,检查是否存在元素t
- count<C,t> :值元函数,返回容器中元素t的个数
- equal<C1,C2> :值元函数,比较两个容器是否等价,即元数据相同,容器可能是不同类型
3)变换算法
变换算法处理容器中的全部或部分元素,然后返回一个新的容器,带前缀”reverse_”的形式可以返回操作后的逆序容器。
- copy<From,To> :拷贝一个容器里所有的元素
- replace<From,Old,New,To> :把容器中的Old元素全部替换为New元素
- remove<From,t,To> :移除容器中所有值为t的元素
- reverse<From,To> :逆序拷贝容器里的所有元素
最后一个参数To可以省略不用,直接用::type返回变换后的新容器,如果使用,那么通常需要搭配插入器工作。
4)运行时算法
mpl中for_each算法可以遍历类型容器,工作在运行时,调用一个函数对象操作类型容器里类型对应的实例对象。
for_each算法是一个模板函数,有两个模板参数,第一个参数是mpl容器,必须显式指定,第二个是函数对象,它应具有模板成员函数operator(),能够处理mpl容器中的所有类型,否则编译错误。
struct mpl_func1{ template<typename T> void operator()(T t) { cout << typeid(t).name() << endl; }};struct mpl_func2{ template<typename T> void operator()(T t) { if(is_same<typename tag<T>::type,integral_c_tag>::value) { cout << t << ','; } }};int main(){ typedef range_c<int, 0, 5> rc; typedef mpl::vector<> vec; typedef mpl::copy<rc, mpl::back_inserter<vec>>::type vec2; typedef push_front<vec2, float>::type vec3; mpl::for_each<vec3>(mpl_func1()); mpl::for_each<vec3>(mpl_func2()); return 0;}
运行结果如下:
floatstruct boost::mpl::integral_c<int,0>struct boost::mpl::integral_c<int,1>struct boost::mpl::integral_c<int,2>struct boost::mpl::integral_c<int,3>struct boost::mpl::integral_c<int,4>0,1,2,3,4,
元编程断言
mpl库在头文件<boost/mpl/assert.hpp>中特意定义了几个元编程断言,能够在元程序发生错误时提供有用的信息。
- 基本断言
宏BOOST_MPL_ASSERT是最常用的一个静态断言,它使用一个返回bool_值元函数pred作为参数,断定pred::value为真,调用形式:BOOST_MPL_ASSERT((pred)); //必须两对圆括号
typedef mpl::vector<int,char> vec;BOOST_MPL_ASSERT((is_same<int,front<vec>::type>)); //注意不需要::valueBOOST_MPL_ASSERT((equal_to<size<vec>::type,int_<3>>));
第二个断言会在编译时报出形如”***pred::***”的错误,同时出现行号。
- 否定断言
BOOST_MPL_ASSERT_NOT - 关系断言
BOOST_MPL_ASSERT_RELATION(x,rel,y)
x,y是两个编译期整数(非包装器),rel是一个合法的C++关系操作符,如==、<。
这个不需要两对圆括号 - 定制消息断言
BOOST_MPL_ASSERT_MSG(c,msg,types_)
c:条件表达式,非元函数
msg:自定义的消息,但不是一个C字符串,而是一个符合C++语法的标志符
types_:类型列表
实例研究
实现一个动态加载动态链接库函数功能的实例研究
先编译一个简单的动态库:
//libtest.h#ifdef __cplusplusextern "C"{#endif int so_func1(int x); int so_func2(int x,int y);#ifdef __cplusplus}#endif
//libtest.cpp#include "libtest.h"int so_func1(int x){ return x*x;}int so_func2(int x,int y){ return x + y;}//g++-4.8 libtest.cpp -fPIC -shared -o libtest.so
1.泛型编程版本
简单地封装了一个so操作类
//gp_version.cpp#include <iostream>#include <stdexcept> //runtime_error#include <dlfcn.h> //UNIX动态链接头文件#include "libtest.h"class DlManager //定义一个加载so函数的包装类{public: DlManager(const char* name) { m_h = dlopen(name,RTLD_NOW); //使用文件名加载so } ~DlManager() { dlclose(m_h); } template<typename FuncType> FuncType load(const char* func_name) { FuncType pf = reinterpret_cast<FuncType>(dlsym(m_h,func_name)); //强制类型转换 if(!pf) { throw std::runtime_error(dlerror()); } return pf; }private: typedef void* handle_t; //句柄类型定义 handle_t m_h; //so文件的句柄};typedef int (*Func1)(int);typedef int (*Func2)(int,int);int main(){ DlManager dm("./libtest.so"); Func1 f1 = dm.load<Func1>("so_func1"); Func2 f2 = dm.load<Func2>("so_func2"); std::cout << f1(10) << std::endl; std::cout << f2(10,20) << std::endl; return 0;}//g++-4.8 gp_version.cpp -ldl -o gp_version
2.元编程第1版
加载动态库这个过程,可以分解成两部分:一部分是编译期的数据,包括文件名、函数名以及函数指针类型,另一部分是运行时代码,包括加载so文件和获取so函数,泛型版本没有把这两者很好地解耦,而是将两者在运行时混合在一起了。
为了明确区分编译和运行时的数据,使用mpl把程序分为前端和后端两个部分,前端使用mpl定义编译期数据,后端使用前端数据实现运行时功能。
前端
前端数据有so文件名、接口函数名和函数指针类型,前两者可以使用mpl::string表述,后者本身就是元数据,为了实现函数名与函数指针类型的对应关系,可以使用mpl::map,最后用struct把这些数据封装成一个前端类。
后端
后端与泛型版本差不多,只不过操作的数据变成了前端的元数据,把后端实现为一个模板类,这样就可以使用不同的前端类支持不同的so加载功能。
//mp_version1.cpp#include <iostream>#include <boost/mpl/vector.hpp>#include <boost/mpl/string.hpp>#include <boost/mpl/map.hpp>#include <boost/mpl/at.hpp>#include <dlfcn.h> //UNIX动态链接头文件#include "libtest.h"namespace mpl = boost::mpl;struct dl_front{ typedef mpl::string<'./','lib','test','.so'> so_name; typedef int (*Func1)(int); typedef int (*Func2)(int,int); typedef mpl::string<'so_','func','1'> func1_name; typedef mpl::string<'so_','func','2'> func2_name; typedef mpl::map< mpl::pair<func1_name,Func1>, mpl::pair<func2_name,Func2> > map_fun;};template<typename Front>class dl_back{private: void* m_h = NULL;public: dl_back() { m_h = dlopen(mpl::c_str<typename Front::so_name>::value,RTLD_NOW); } ~dl_back() { dlclose(m_h); } template<typename FuncName> typename mpl::at<typename Front::map_fun,FuncName>::type //返回类型 func_ptr() { typedef typename mpl::at<typename Front::map_fun,FuncName>::type result_type; result_type pf = reinterpret_cast<result_type>(dlsym(m_h,mpl::c_str<FuncName>::value)); return pf; }};int main(){ dl_back<dl_front> dl; std::cout << dl.func_ptr<dl_front::func1_name>()(10) << std::endl; std::cout << dl.func_ptr<dl_front::func2_name>()(10,20) << std::endl; return 0;}//g++-4.8 mp_version1.cpp -ldl -o mp_version1
在调用成员函数func_ptr()时,除了显式写出前端定义的函数名外,还必须调用两次operator(),因为第一次调用只是获取了函数指针,第二次调用材质真正的so接口函数调用。
3.元编程第2版
第一版的元编程还可以增强成员函数func(),直接传递参数减少一次operator()调用。要减少一个operator()调用,就要求函数func()返回的是so函数指针的返回类型而不是函数指针类型,同时传递相应数量的参数,前者可以使用result_of结合mpl来自动推导函数的返回值类型,后者可以用可变模板参数列表解决。
改进后func()代码:
template<typename FuncName,typename ... Args> //可变参数模板 typename boost::result_of< //使用result_of推导函数的返回值 typename mpl::at<typename Front::map_fun,FuncName>::type(Args...) >::type func(Args const& ...args) { typedef typename mpl::at<typename Front::map_fun,FuncName>::type func_type; //得到函数指针类型 func_type pf = reinterpret_cast<func_type>(dlsym(m_h,mpl::c_str<FuncName>::value)); //获取函数指针 return pf(args...); //直接调用函数指针 }
也可以直接使用第一版写好的func_ptr()函数直接返回函数指针来调用:
template<typename FuncName,typename ... Args> //可变参数模板 typename boost::function_traits< //改用function_traits推导类型 typename boost::remove_pointer< //需要先移除类型里的指针 typename mpl::at<typename Front::map_fun,FuncName>::type >::type >::result_type //获取函数的返回值 func_V(Args const& ... args) { return func_ptr<FuncName>()(args...); }
完整代码:
//mp_version2.cpp#include <iostream>#include <boost/mpl/vector.hpp>#include <boost/mpl/string.hpp>#include <boost/mpl/map.hpp>#include <boost/mpl/at.hpp>#include <boost/utility/result_of.hpp>#include <boost/type_traits.hpp>#include <dlfcn.h> //UNIX动态链接头文件#include "libtest.h"namespace mpl = boost::mpl;struct dl_front{ typedef mpl::string<'./','lib','test','.so'> so_name; typedef int (*Func1)(int); typedef int (*Func2)(int,int); typedef mpl::string<'so_','func','1'> func1_name; typedef mpl::string<'so_','func','2'> func2_name; typedef mpl::map< mpl::pair<func1_name,Func1>, mpl::pair<func2_name,Func2> > map_fun;};template<typename Front>class dl_back{private: void* m_h = NULL;public: dl_back() { m_h = dlopen(mpl::c_str<typename Front::so_name>::value,RTLD_NOW); } ~dl_back() { dlclose(m_h); } template<typename FuncName> typename mpl::at<typename Front::map_fun,FuncName>::type //返回类型 func_ptr() { typedef typename mpl::at<typename Front::map_fun,FuncName>::type result_type; result_type pf = reinterpret_cast<result_type>(dlsym(m_h,mpl::c_str<FuncName>::value)); return pf; } template<typename FuncName,typename ... Args> //可变参数模板 typename boost::result_of<typename mpl::at<typename Front::map_fun,FuncName>::type(Args...)>::type //使用result_of推导函数的返回值 func(Args const& ... args) { typedef typename mpl::at<typename Front::map_fun,FuncName>::type func_type; //得到函数指针类型 func_type pf = reinterpret_cast<func_type>(dlsym(m_h,mpl::c_str<FuncName>::value)); //获取函数指针 return pf(args...); //直接调用函数指针 } template<typename FuncName,typename ... Args> //可变参数模板 typename boost::function_traits<typename boost::remove_pointer<typename mpl::at<typename Front::map_fun,FuncName>::type>::type>::result_type //获取函数的返回值 //改用function_traits推导类型 func_V(Args const& ... args) { return func_ptr<FuncName>()(args...); }};int main(){ dl_back<dl_front> dl; std::cout << "----------------mp version 2-----------" << std::endl; std::cout << dl.func<dl_front::func1_name>(10) << std::endl; std::cout << dl.func<dl_front::func2_name>(10,20) << std::endl; std::cout << "----------------mp version 2-----------" << std::endl; std::cout << dl.func_V<dl_front::func1_name>(10) << std::endl; std::cout << dl.func_V<dl_front::func2_name>(10,20) << std::endl; return 0;}//g++-4.8 mp_version2.cpp -ldl -o mp_version2
- 模板--模板元编程
- 初探模板元编程
- 模板元编程
- C++模板元编程
- 模板元编程
- 模板元编程
- C++模板元编程
- C++模板元编程
- 模板元编程-C++
- C++模板元编程
- 模板元编程(二)
- 模板元编程练习
- 模板元编程练习
- 模板元编程简介
- C++模板元编程
- 模板元编程
- c++模板元编程
- 模板元编程
- 服务器后端servlet中文信息返回,使用response乱码的问题及setCharacterEncoding()与setContentType()区别
- 简单的TS入门
- SpringMVC——接收请求参数和页面传参
- JavaScript数组遍历的几种方式
- 排序
- 模板元编程
- 对caffe2的一些初步体会(草稿)
- PG10 中pg_current_wal_insert_lsn()和pg_walfile_name()的使用
- C++深拷贝与浅拷贝(实现String类)
- echarts世界国家中英文对照
- 饿了么组件库element-ui正则表达式验证表单,后端验证表单
- HDU
- 机器学习概念(1)
- python学习笔记-01