c++11-模板元实战
前言
黑魔法,应用场景 :
1.实现宿生语言
2.实现一些常规手段做不到的东西,比如 c++11::share_prt::enable_share_from_this
3.作为实现各种库的基本组件 :Stl,Boost,标准库都大量运用了模板元技术
很多人比较排斥这个东西...个人觉得实用就行,工具终究是为人服务的
just enjoy it 😃
推荐的资料
《C++ Templates》
《C++模板元编程》
C++11模板元入门 https://www.cnblogs.com/qicosmos/p/4480460.html
C++高质量社区 http://purecpp.org/
Part 1 实现一个基于C++的宿生Lisp方言
基本约定
建议先看看江南的博客入个门 :C++11模板元入门 https://www.cnblogs.com/qicosmos/p/4480460.html
这是一个类型的世界,首先定义三种基本类型
1.integral_constant :与值关联的类型
2.元类型
3.元函数
1.integral_constant
c++11中的标准库元类型,先看源代码
template <typename T,T v>
struct integral_constant
{
using value_type = T ;
using type = integral_constant<T,v>;
static constexpr T value = v;
constexpr operator value_type()
{
return value;
}
};
看几个栗子
using true_type = integral_constant<bool, true>;
using ten = integral_constant<int,10>;
ten::value_type val = 10;
cout << ten::type::type::type::value << endl;
cout << ten() << endl;
typedef和uisng指令等效,using指令更清晰
可以观察到 integral_constant可以生成一个新类型,新类型绑定一个值和该值的类型
并且特别的是 :integral_constant::type指向类型本身
类型2.元类型
特征十分明显 :结构体为空 (建议元编程的时候全部采用struct,struct与class没什么区别,仅仅默认访问权限、继承全部为public
template<typename a,typename b>
struct sum : integral_constant<typename a::value_type,a::value + b::value>
{};
int main()
{
using ten = integral_constant<int,10>;
using five = integral_constant<int,5>;
cout << sum<ten,five>::value << endl;
return 0;
}
继续观察样例,可以看到sum完成了两个数的加法
观察点1 : 第二行 typename a::value_type,为什么要加奇怪的typename?因为c++模板的特殊性质,编译器在看到a::value_type的时候并不能分析出来a::value_type是成员变量还是类型,所以需要显式说明
观察点2 : sum的结构体为空,所以sum是元类型,但是,sum却完成一个元函数行为 :计算两数和。既然元类型可以进行元计算,为什么我们还需要划分出元函数?
答案是元类型可以进行元计算,但是完成不了复杂的元计算,因为在元类型做元计算的时候,声明不了新类型,导致只能实现一句代码的元计算,做不了复杂计算
类型3.元函数
特征也十分明显:没有继承行为,结构体内部充满一些using指令,用来做元计算
template<typename a,typename b>
struct sum
{
using result = integral_constant<typename a::value_type,a::value + b::value>;
};
int main()
{
using ten = integral_constant<int,10>;
using five = integral_constant<int,5>;
cout << sum<ten,five>::result() << endl;
return 0;
}
类似于c/c++中的函数,元函数也有"函数名" : sum,"函数参数" :typename a,typename b,"返回值" result
观察点1.我们约定,元函数的"返回值"全部定义为result
观察点2.cout << sum<ten,five>::result() << endl; result是类型,我们可以通过类似 cout << int() << endl;的形式来打印元信息,重载一下流输出即可,后面还会涉及
观察点3.我们约定,元函数不继承任何类型
lisp方言基本类型与操作
1.基本类型,如整形,字符串
2.null类型
3.pair类型
4.元函数car,cdr
有一个非常庞大的证明和实践体系证明上述简单类型/操作能实现非常复杂的计算,是图灵完备的
1.基本类型
整形
template<int val>
struct number : integral_constant<int,val> {};
字符串 : 因为c++11不滋滋模板字符串,所以不能直接定义(因为 "string"的类型是const cahr* const 两个相同的字符串常量拥有不同的常量地址,模板不能识别这种
因为本次模板元实战主要在模板,我采用了一种取巧的办法 :把字符串压进unsigned long long int(简记为ull,可以滋滋长度在8及以下的任何字符串
constexpr ull stoull(const char *p,ull now = 0)
{
return *p == 0 ? now : stoull(p + 1,now * 255 + *p);
}
template<ull val>
struct string : public integral_constant<ull,val> {};
#define str(x) string<stoull(x)>
这样就可以很方便的用str("string")来产生字符串类型
浮点 : 因为c++的历史原因,模板也不滋滋浮点常量,取巧的办法是定义一个pair<int,int> 即"int.int",与主题关系不大,故不再赘述
2.null类型
为什么要把null单独说明呢...因为空类型相当重要,设计良好的空类型可以很好的简化很多设计
struct null : str("null") {};
null也可以定义成别的样子,这样方便一些
3.pair类型
和c++中的pair类似,只不过first -> car,second -> cdr(仅仅是遵循lisp方言惯例,没什么特别含义
template<typename T,typename U>
struct pair
{
using car = T;
using cdr = U;
};
4.元函数car,cdr
#define car(x) typename Car<x>::result
template<typename T>
struct Car
{
using result = typename T::car;
};
#define cdr(x) typename Cdr<x>::result
template<typename T>
struct Cdr
{
using result = typename T::cdr;
};
#define equal(typea,typeb) typename std::is_same<typea,typeb>::type
#define if_else(a,b,c) typename std::conditional<a,b,c>::type
顺便包装一下std::is_same std::conditional
元函数约定 :
1.每一个元函数都会搭配一个宏
2.统一用result作为元函数返回值
3.元函数不继承任何类型
实现一个异类字典
先看效果
using table = list<pair<str("a"),str("value_a")>,
pair<str("b"),integral_constant<bool,0>>,
pair<number<10>,str("x")>
>;
using resulta = table_find(table,str("a"));
using resultb = table_find(table,number<10>);
using resultc = table_find(table,str("c"));
cout << resulta() << endl; //value_a
cout << resultb() << endl; //x
cout << resultc() << endl; //null
看起来很复杂?我们分三步实现
1.实现list
2.实现table_find
3.实现输出
1.实现list
template<typename T,typename ...arg>
struct list : pair<T,list<arg...>> {};
template<typename T>
struct list<T> : pair<T,null> {};
不会模板参数包的建议百度
概念上list<a,b,c> = pair<a,pair<b,pair<c,null>>>;
利用参数包 + pair不断展开即可
template<typename T,typename ...arg>
struct list<T,arg...> : pair<T,list<arg...>> {};
template<typename T>
struct list<T> : pair<T,null> {};
因为list对类型没有啥要求,所以直接实现了一个异类字典
2.实现table_find
#define table_find(table,value) typename Table_find<table,value>::result
template<typename list,typename key>
struct Table_find
{
using if_found = cdr(car(list));
using if_not_found = table_find(cdr(list),key);
using check_equal = equal(car(car(list)),key);
using result = if_else(check_equal::value,if_found,if_not_found);
};
template<typename key>
struct Table_find<null,key>
{
using result = null;
};
首先定义宏,得益我们良好的约定,我们可以写出非常阳间的代码
递归终止的条件十分简单:找不到返回null即可,注意偏特化的写法
概念上,table_find里面核心就是一个if_else逻辑
代码写的十分清楚,不停的递归list结构,层层寻找即可
注意using check_equal = equal(car(car(list)),key);
因为我们的宏中有typename
所以equal(car(car(list)),key)::value;
的代码是错误的,因为value是个静态成员,解决办法是加个间接类型
实现输出
下面结尾c++输出风格,如果你喜欢printf()这样的,原理一样
cout << integral_constant<int,10>() << endl; // 10
cout << table_find<table,str("12")>::result() << endl; // null
首先integral_constant里面重载了类型转换运算符,编译器会自动转化,我们只需要写自己类型的输出格式即可
template<ull val>
ostream& operator << (ostream& os,string<val>)
{
std::string ret;
ull now = val;
while (now)
ret.push_back(now % 255),
now /= 255;
reverse(ret.begin(),ret.end());
return os << ret;
}
ostream& operator << (ostream& os,null)
{
return os << "null";
}
template<typename T,typename U>
ostream& operator << (ostream& os,pair<T,U>)
{
return os << "(" << T() << "," << U() <<")";
}
注意ostream& operator << (ostream& os,string
因为信息都在类型里面,参数写不写无所谓,你写的话,编译器自动会优化掉
调用的时候func(type()),编译器就能演绎类型了,func(type)这样是不行滴,因为违背了c\c++参数传递规则
就是函数重载 + 模板特化,大多数输出就这三种特化方式...都在这了...比较简单,照猫画虎即可
list<number<10>,str("22")> : [10,22]
list的输出比较麻烦一点,因为要控制输出格式 + 参数包,代码里有,原理没区别,不赘述了
小结
至此,我们实现了一个字典...难者不会,会者不难...写熟练之后还是挺简单的
小练习,实现一个length的元函数,计算list,字典的元素个数
多动手,练习是必须滴
#define length(type) typename Length<type>::result
template<typename T>
struct Length
{
using len = length(cdr(T));
using result = number<1 + len::value>;
};
template<>
struct Length<null>
{
using result = number<0>;
};
实现一个归并排序
1.实现merge : 按顺序合并两个list得到新的list
2.实现split : 切割一个list为长度差不超过1的两个list
3.实现sort_merge : 合并两个有序list为一个有序list
4.实现sort : 排序一个list
1.实现merge : 按顺序合并两个list得到新的list
这里需要一个技巧 : 模板的模板参数,不会的百度一下
模板的模板参数,在这里的作用是拿到list内部的类型,常规手段拿不到
其实模板的模板参数类似于函数的实参演绎,只不过挪到了编译期,作用依旧是推导类型
#define merge(typea,typeb) typename Merge<typea,typeb>::result
template<typename list1,typename list2>
struct Merge {};
template<typename ...args1,template<typename ...args1> class list1,
typename ...args2,template<typename ...args2> class list2
>
struct Merge<list1<args1...>,list2<args2...>>
{
using result = list<args1...,args2...>;
};
template<typename ...args1,template<typename ...args1> class list1>
struct Merge<list1<args1...>,list<null>>
{
using result = list<args1...>;
};
template<typename ...args2,template<typename ...args2> class list2>
struct Merge<list2<null>,list2<args2...>>
{
using result = list<args2...>;
};
结构就是一个声明 + 4个偏特化...后面三个偏特化很简单,重点看第一个偏特化
template<typename ...args1,template<typename ...args1> class list1,
typename ...args2,template<typename ...args2> class list2
>
struct Merge<list1<args1...>,list2<args2...>>
{
using result = list<args1...,args2...>;
};
这里的merge其实可以通过lisp形式完成...(偷个懒顺便练习一下模板的模板参数...后面还会用lisp的风格写元函数的
元函数体里面很直观,就是拿到两个list的参数,合并到一个新的list就完了
重点在于模板的模板参数,因为是偏特化,所以template中的顺序无所谓,编译器会推导出正确的类型
template<typename ...args1> class list1因为模板类才有模板的模板参数,c++要求在这里写一个class...(比较憨,但是你得写...以后的标准可能改进这一点...
merge就没了...(偷懒成功,大雾
2.实现split : 切割一个list为长度差不超过1的两个list
split的实现就很lisp了,先康代码
#define split(type) typename Split<type>::result
#define _split(type,typeb,typec) typename Split<type,typeb,typec>::result
template<typename T,typename first = list<null>,typename second = list<null>>
struct Split
{
using new_first = merge(first,list<car(T)>);
using new_second = merge(second,list<car(T)>);
using len_first = length(new_first);
using len_second = length(new_second);
using result = if_else(len_first::value < len_second::value,
_split(cdr(T),new_first,second),
_split(cdr(T),first,new_second));
};
template<typename first,typename second>
struct Split<null,first,second>
{
using result = pair<first,second>;
};
老样子,先看简单的第二个特化...
split概念上是一个带参数的dfs(T,first,second),运算结果存在参数里面,T为空的时候运算结束,返回参数作为计算结果
再看
#define split(type) typename Split<type>::result
#define _split(type,typeb,typec) typename Split<type,typeb,typec>::result
template<typename T,typename first = list<null>,typename second = list<null>>
struct Split
{
using new_first = merge(first,list<car(T)>);
using new_second = merge(second,list<car(T)>);
using len_first = length(new_first);
using len_second = length(new_second);
using result = if_else(len_first::value < len_second::value,
_split(cdr(T),new_first,second),
_split(cdr(T),first,new_second));
};
注意偏特化不能有默认参数
思路很直观,比较一下两个参数new_first,new_second的长短,给短的添加当前元素
这里有一个阴间坑 : 如果你split实现的不对,比如 [1,2] -> [1,2],[] 也就是没有正确切割...split会编译通过,但是在某些计算中模板会报一大推错...
老样子,if_else实现一下,给短的添加一个元素即可...注意我们不能销毁、改写一个已经存在的类型...必须生成新的类型,代码很直观...
其实这样子的元函数已经和普通函数差别不大了...只不过把值的运算变成了类型的运算
3.实现sort_merge : 合并两个有序list为一个有序list
#define sort_merge(typea,typeb) typename Sort_merge<typea,typeb>::result
#define _sort_merge(typea,typeb,typec) typename Sort_merge<typea,typeb,typec>::result
template<typename list1,typename list2,typename ret = list<null>>
struct Sort_merge
{
using car1 = car(list1);
using car2 = car(list2);
using cdr1 = cdr(list1);
using cdr2 = cdr(list2);
using ret1 = merge(ret,list<car1>);
using ret2 = merge(ret,list<car2>);
using result = if_else(car1::value <= car2::value,
_sort_merge(cdr1,list2,ret1),
_sort_merge(list1,cdr2,ret2));
};
template<typename list1,typename ret>
struct Sort_merge<list1,null,ret>
{
using result = merge(ret,list1);
};
template<typename list2,typename ret>
struct Sort_merge<null,list2,ret>
{
using result = merge(ret,list2);
};
老样子,先看简单的偏特化:)
和slpit类似,计算结果带在参数ret里面
只有一个list的时候,直接和ret合并返回,注意合并顺序
#define sort_merge(typea,typeb) typename Sort_merge<typea,typeb>::result
#define _sort_merge(typea,typeb,typec) typename Sort_merge<typea,typeb,typec>::result
template<typename list1,typename list2,typename ret = list<null>>
struct Sort_merge
{
using car1 = car(list1);
using car2 = car(list2);
using cdr1 = cdr(list1);
using cdr2 = cdr(list2);
using ret1 = merge(ret,list<car1>);
using ret2 = merge(ret,list<car2>);
using result = if_else(car1::value <= car2::value,
_sort_merge(cdr1,list2,ret1),
_sort_merge(list1,cdr2,ret2));
};
很直观了...这里再强调一下,为什么,元类型也可以计算,但是我们不用
元类型通过继承做元计算的时候,没办法声明新类型
这导致了它的计算能力大大下降...直接写元函数,简单直观,没必要用一些诡异的技巧折磨自己
sort_merge就没了
4.实现sort : 排序一个list
万事俱备...轻松愉快
#define sort(type) typename Sort<type>::result
template<typename T>
struct Sort
{
using left = car(split(T));
using right = cdr(split(T));
using result = sort_merge(sort(left),sort(right));
};
template<typename T>
struct Sort<list<T>>
{
using result = list<T>;
};
是8是很直观...某种意义上编译期排序并不比普通排序难...来个栗子
因为函数式编程语言在语法简洁性上有巨大优势
编译期计算8皇后
#include <bits/stdc++.h>
using namespace std;
int dfs(int n,int now = 0,int zuo = 0,int you = 0,int has = 0)
{
if ((1 << n) - 1 == has)
return 1;
int ret = 0;
int state = zuo | you | has;
state = ((1 << n) - 1) & (~state);
for (int i = 0;i < 32;i++)
if ((1 << i) & (state))
{
int pos = (1 << i);
ret += dfs(n,now + 1,zuo + pos << 1,you + pos >> 1,has + pos);
}
return ret;
}
int main()
{
int n;
cin >> n;
cout << dfs(n);
return 0;
}
我们把这个良好的实现挪到编译期就行了...八皇后的实现还是比较复杂了...包含了一部分元编程难点...有兴趣的自己看看代码
https://www.cnblogs.com/XDU-mzb/p/14832089.html
完整实现过一遍模板元实现lisp,对编程能力有一个巨大的锻炼
本文来自博客园,作者:XDU18清欢,转载请注明原文链接:https://www.cnblogs.com/XDU-mzb/p/14804536.html