参考链接:https://github.com/MagicBowen/tlp/tree/master/include/tlp、C++11 模板元编程 - 目录 - 简书
具体代码实现:https://github.com/pisuto/tinySTLLearning/tree/master/Test/TemplateMetaProgramLearning
一、数值结构
在实现快速排序之前,首先是定义存储数据的类型结构。在模板元编程中,每一个值其实是使用一个一个类型表示,那么就需要定义一个类,作为所有数值类型的父类。派生类只要使用父类定义的值和类型即可。
// num type
template<typename T, T N>
struct num_type
{
using value_type = T;
static constexpr T value = N;
};
template<int N>
struct int_type : num_type<int, N> {};
// bool
template<bool b>
struct bool_type : num_type<bool, b> {};
二、基本的操作类
在定义了数值类型之后,对这些数值的操作是必不可少的,类似于函数一样。以数值比较为例子,对于>=的操作是需要通过模板来实现的,因为数值类型本身就是类。另外>大于号好像在模板中不能被区分。
template<typename H, typename T>
struct type_greater
{
// >大于号好像不能很好区分
using result = bool_type < T::value < H::value>;
};
// 尝试实现其他的模板类型
// type_add 实现数值叠加并获得结果
// type_is_same 判断两个类型是否一致
// type_negate 对bool_type类型进行取反
// ... ...
//
除了一些数值操作类的函数的实现,也需要实现有if和for语句功能的模板类。这里的if语句使用模板特化来实现,通过true_result和false_result来进行区分。而for循环(放在讲list的时候重点讲解)则是需要通过递归来实现。
using false_result = bool_type<false>;
using true_result = bool_type<true>;
// 根据结果选择不同的分支
template<typename B, typename H, typename T>
struct type_is_choose;
template<typename H, typename T>
struct type_is_choose<true_result, H, T>
{
using result = H;
};
template<typename H, typename T>
struct type_is_choose<false_result, H, T>
{
using result = T;
};
对于判断两个类型是否可以互相转化也可以通过模板来实现,这里最主要的是采用函数重载来实现不同类型的区分。“...”表示的是可以输入任意个数的形参。需要注意的是其中的注释部分,如果仅仅是用返回值给bool_type赋值这是有问题的,因为其是在运行时决定的,这里必须是在编译时就决定,因此采用size_of来处理。
template<typename B, typename D>
struct type_is_convertible
{
//private:
// static D self();
// static bool test(...) { return false; };
// static bool test(B) { return true; };
//public:
// // 这种方法不行是因为返回值是在运行时候决定的
// using result = bool_type<test(self())>;
private:
using Yes = char;
struct No { char dummy[2]; };
static Yes test(B);
static No test(...);
static D self();
public:
using result = bool_type<sizeof(Yes) == sizeof(test(self()))>;
};
类似地,还有type_is_base_of来判断一个类是否是另一个类的基类,那么就可以用type_is_convertible来判断D*是否可以向B*进行转换。需要注意的是D和B不能是同一类型,否则就不符合该类型的意义。另外,两个类的类型不能都是void*。
template<typename B, typename D>
struct type_is_base_of
{
private:
using condition_not_same_type = typename type_and<
typename type_negate<typename type_is_same<B, D>::result>::result,
typename type_negate<typename type_is_same<void*, D*>::result>::result>::result;
public:
using result = typename type_and<condition_not_same_type,
typename type_is_convertible<B*, D*>::result>::result;
其余还有许多用于操作的类,必须自己学会动手实现。
// 两个数值类型的值是否相等
type_is_equal
// 为bool_type结果取反
type_negate
// 为两个bool_type结果取与
type_and
// 判断类型是否有效得到bool_type
type_is_valid
// 实现>=
type_greater_equal
//
// ... ...
三、使用模板实现LIST
为了存储一组数据,在常规的操作中是创建一个数组变量,例如new int[5]或者直接使用std::vector。而在模板中要存储变量必须在类型里面进行嵌套,而数值则是作为一个模板参数存在。因此,需要创建一个类型用于连接前后的数据(其实从这里可以看出,相对于list里的元素,这个数据结构更像是单链表里的node),即:
template<typename H, typename T>
struct type_elem
{
using head = H; // 真正保存的数据
using tail = T; // 连接后续的数据
};
// null
struct null_type {};
这里的模板参数是一个类型,那么要怎么存储数据呢?这就用到了前面定义的数值类型int_type。
以存储一个数据为例子:type_elem<1, null_type>
以存储两个数据为例子:type_elem<1, type_elem<2, null_type>>
以存储三个数据为例子:type_elem<1, type_elem<2, type_elem<3, null_type>>>
从中可以看到数据是以递归的形式一层嵌套一层存在的,那么和函数里的递归一样需要递归函数和终止条件。
template<typename H, typename... Ts>
struct type_list
{
using result = type_elem<H, typename type_list<Ts...>::result>;
// static constexpr auto size = sizeof(Ts)...;
};
template<typename H>
struct type_list<H>
{
using result = type_elem<H, null_type>;
};
// for example
using list = typename type_list<int_type<2>, int_type<1>, int_type<2>, int_type<0>, int_type<3>>::result;
四、LIST的工具类
在完成list的定义之后,为了完成快速排序,还需要编写类似于原生list、vector等容器的工具函数一样的工具类。
以push为例子,在list最后添加一个元素,那么就需要遍历list直至末尾,并加上一个type_elem类。同理的append结合两个list也是如此,在最后末尾添加上list2即可。
// push
template<typename TL, typename V>
struct type_push;
template<typename T>
struct type_push<null_type, T>
{
using result = typename type_elem<T, null_type>;
};
template<>
struct type_push<null_type, null_type>
{
using result = null_type;
};
template<typename H, typename T, typename V>
struct type_push<type_elem<H, T>, V>
{
using result = type_elem<H, typename type_push<T, V>::result>;
};
// append
template<typename TL1, typename TL2>
struct type_append;
template<typename H1, typename TL2>
struct type_append<type_elem<H1, null_type>, TL2>
{
using result = type_elem<H1, TL2>;
};
template<typename H1, typename T1, typename TL2>
struct type_append<type_elem<H1, T1>, TL2>
{
using result = typename type_elem<H1, typename type_append<T1, TL2>::result>;
};
为了实现quick_sort,其中的筛选操作是必不可少的,通过type_filter应用某一规则获得适应该规则的所以元素形成list,丢弃所以不满足规则的元素。这就必须使用二选一的操作类,即前文构建的type_is_choose。
template<typename TL, typename V, template<typename S1, typename S2> class Pred>
struct type_filter;
template<typename V, template<typename S1, typename S2> class Pred>
struct type_filter<null_type, V, Pred>
{
using result = null_type;
};
template<typename H, typename T, typename V, template<typename H, typename V> class Pred>
struct type_filter<type_elem<H, T>, V, Pred>
{
using result = typename type_is_choose<typename Pred<H, V>::result,
type_elem<H, typename type_filter<T, V, Pred>::result>,
typename type_filter<T, V, Pred>::result>::result;
};
// 还有许多工具类
//
// 获取list的长度
type_length
// 获取某一位置的元素
type_at
// 元素是否被包含
type_is_included
// 删除某一元素
type_erase
// 去除重复的元素
type_unique
// 判断list1是否是list2的子集
type_is_subset
// ... ...
五、快速排序
实际上用函数写快速排序就是一个简单的思想,将数据集以主元为标准将小于主元的数放在主元的左边,将大的数放在主元的右边,然后再在这两数据集中继续递归,即:
那么就依照以上的思想就可以在模板中实现快速排序。
template<typename TL>
struct type_quick_sort;
template<>
struct type_quick_sort<null_type>
{
using result = null_type;
};
template<typename H, typename T>
struct type_quick_sort<type_elem<H, T>>
{
using result = typename type_append<
typename type_push<typename type_filter<typename type_quick_sort<T>::result, H, type_less>::result, H>::result,
typename type_filter<typename type_quick_sort<T>::result, H, type_greater_equal>::result
>::result;
};
// 切记不能忘记result
using quick_sort = typename type_quick_sort<list>::result;