数据字典模板_C++ 模板元编程:一种屠龙之技
概述
模板元编程可在编译时完成一些计算,说它是屠龙之技,是因为模板元编程
- 似乎很厉害的样子。
- 似乎没有地方可以用上。
假如只从实际工程出发,没有太大必要研究模板元编程。只是我还是想写写这个主题,感叹一下模板居然可以做这种事情。
本文假设大家知道 C++ 模板元编程、编译期计算等概念。先上完整代码:
MetaLispgithub.com
可以对照着代码看下文。代码中,首先用 C++ 模板来模拟 Scheme 的常用函数。再将 Scheme 代码改写成 C++ 模板,实现一些计算。基本上任何 Scheme 代码都可以按照这种方式改写。
代码中包含一些元编程例子。简单例子是计算阶乘、计算平方根。复杂点的例子是,求解八皇后、huffman 编码。
编程语言特性
假如将模板当成一门全新的编程语言。一门编程语言最少应该具有什么特性呢?
- 需要有基本的数据类型,比如数字、字符串、布尔值等。
- 需要某种方法,将基础类型组合起来,表达更高级的概念。比如 C 语言中的 struct, 就是一种组合方法。数组和字典也是组合方法。
- 需要流程控制。典型的是顺序、分支、循环。
- 需要输入和输出。
还需要什么语言特性呢?已经没有了,具备上述的特性,基本就可写出任何程序。
下面按照这个顺序,描述一下模板元编程。
基本数据类型
数字
数字可分为整数和小数。怎么用模板类型表示整数呢?大概这样
template <int N>
struct number {static const int value = N;
};
这样每个整数都对应一种类型。那小数呢?C++ 的模板参数不可以是 double
, 下面的语法是错误的
template <double N>
struct number {static const double value = N;
};
但是我们可以将小数表示成分数,有分子和分母。于是数字可以表示为
template <int64_t N, int64_t D = 1>
struct number {static const int64_t numer = N;static const int64_t denom = D;
};
当分母为 1 时,其实就是整数。当然我们需要简化分子和分母,在编译期实现 gcd
(求最大公约数)算法。
布尔值
布尔值其实可归类为数字。但经常将布尔值单独拆分出来,用于条件判断。布尔值可表达为如下的模板类型。
template <bool flag>
struct boolean {static const bool value = flag;
};
字符串
接下来是用模板类型来表示字符串。C++ 的模板参数只能是整数,不能是 const char*
。但是我们可以将字符串的字符分拆开,这样每个字符都是整数。
比如字符串 hello
对应于类型 String<'h', 'e', 'l', 'l', 'o'>
。
有些编程语言,会将实际上的字符串,再分拆成两种类型。
- 符号(Symbol)
- 字符串(String)
符号是不可修改的,放在全局唯一的符号表。要判断符号是否相同,只需要判断符号对应的指针或者索引是否相同。而字符串是可以被修改的。
Scheme 区分了符号和字符串。为跟 Scheme 对应(我们在用 C++ 模板来模拟 Scheme),我们将在编译期不可变的字符串,叫做符号。符号的类型为
template <char... C>
struct Symbol {static constexpr char const value[sizeof...(C) + 1] = {C..., '0'};
};
为了方便编写代码。提供了一个宏 symbol
symbol("hello") // 生成 Symbol<'h', 'e', 'l', 'l', 'o'>
将基础类型组合起来
cons
有了基础类型,现在需要将基础类型组合起来。最简单的组合方式,就是将两个类型放在一起。
template <typename T, typename U>
struct pair {using car_ = T;using cdr_ = U;
};
第一个类型是 car
,第二个类型是 cdr
。另外我们用 cons
函数,将两个类型粘到一起。于是有
template <typename x>
struct car {using type = typename x::car_;
};template <typename x>
struct cdr {using type = typename x::cdr_;
};template <typename T, typename U>
struct cons : public pair<T, U> {};
car
、cdr
、cons
都是 Scheme 的叫法。现在就可以用 cons
组合两个基本类型,也可以用 car
或 cdr
访问类型
using p = cons<number<1>, number<2>>
using num1 = car<p>;
using num2 = cdr<p>;
假如要组合三个类型,可以嵌套 cons
。
using p = cons<number<1>, cons<number<2>, number<3>>>
list
现在我们用 cons
嵌套来表示线性结构,引入 list
。类似链表,将多个节点串连在一起。为达到这个目标,还需要引入一个结束类型。
struct null {};
比如
using lst0 = list<number<1>, number<2>, number<3>>; // 等价于下面
using lst1 = cons<number<1>, cons<number<2>, cons<number<3>, null>>>;
list
的实现使用了模板偏特化
template <typename... T>
struct list;template <typename T>
struct list<T> : public pair<T, null> {};template <typename T, typename... X>
struct list<T, X...> : public pair<T, list<X...>> {};
字典
好了,有了基本类型和 list
操作(其实是 cons
的嵌套),我们就可以表达任意更高级的概念。比如字典
a: 1
b: 2
就可以表达为
list<symbol("*table*"),cons<symbol("a"), number<1>>,cons<symbol("b"), number<1>>>
类型 tag
我们有了 number
、symbol
、null
、pair
、boolean
这些类型。现在我们想判断类型,比如有一些 is_null
、is_number
等实用函数。用于对应 Scheme 的 null?
、`pair?
等函数。
这可以用经典的模板编程技术 traits
来解决。定义好一个 tag,再在基本类型中嵌入 tag
。比如
struct boolean_tag {static const bool is_pair = false;static const bool is_null = false;static const bool is_number = false;static const bool is_boolean = true;static const bool is_symbol = false;
};struct number_tag {static const bool is_pair = false;static const bool is_null = false;static const bool is_number = true;static const bool is_boolean = false;static const bool is_symbol = false;
};template <bool flag>
struct boolean {using tag = boolean_tag;static const bool value = flag;
};template <int64_t N, int64_t D = 1>
struct number {using tag = number_tag;static const int64_t numer = impl::rat_reduce<(N == 0 || D == 0), N, D>::numer;static const int64_t denom = impl::rat_reduce<(N == 0 || D == 0), N, D>::denom;
};
这里 tag 就是类型标识。每个类型都有了 tag,就可以编写判断函数
template <typename T>
using is_null = boolean<T::tag::is_null>;template <typename T>
using is_pair = boolean<T::tag::is_pair>;template <typename T>
using is_number = boolean<T::tag::is_number>;template <typename T>
using is_boolean = boolean<T::tag::is_boolean>;template <typename T>
using is_symbol = boolean<T::tag::is_symbol>;
流程控制
顺序
顺序是是简单的,一个个写下去就行。比如
using lst0 = list<number<1>, number<2>, number<3>>;
using lst1 = cons<number<1>, cons<number<2>, cons<number<3>, null>>>;display<lst0>();
display<lst1>();
分支
在 C++ 模板中,分支使用偏特化来实现
template <typename Flag, typename True, typename False>
struct if_else_impl : public True {};template <typename True, typename False>
struct if_else_impl<boolean<false>, True, False> : public False {};template <typename Flag, typename True, typename False>
struct if_else : public if_else_impl<typename Flag::type, True, False> {};
if_else
有三个参数,第一个是条件,条件成立就做 True
分支,不成立就走 False
分支。
循环
模板元编程,没有直接的循环语法。但我们可以用递归来实现。
if_else
例子
上面我们定义了 list
, 但还不能操作 list
。现在我们已经有了分支和递归,可以编写一些函数。其中 length
计算 list
的长度,list_ref
用于访问元素。
template <typename items>
struct length {using type = typename if_else<is_null<items>,number<0>,add<number<1>, length<cdr<items>>>>::type;using tag = typename type::tag;
};template <typename items, typename n>
struct list_ref {using type = typename if_else<is_equal<n, number<0>>,car<items>,list_ref<cdr<items>, sub<n, number<1>>>>::type;using tag = typename type::tag;
};
上面的定义看起来很复杂的样子。但上面的模板代码,完全等价于下面的 Scheme 代码
(define (list-ref items n)(if (= n 0)(car items)(list-ref (cdr items) (- n 1))))(define (length items)(if (null? items)0(+ 1 (length (cdr items)))))
来到这样,应该意识到,我们是用 C++ 的模板语法来写 Scheme。基本上 Scheme 的任何代码,都可以按照这种方式改写。
拿 struct length
来说,其中类型名 length
可以对应于 Scheme 的一个函数名。typename items
可以对应于 Scheme 的函数参数。using type
对应于函数返回结果。using tag
是返回结果的类型。
类型 type
我们统一用 T::type
来获取返回结果。T::tag
获取返回类型。为了达到这种统一,在所有的类型中,都定义 type
和 tag
这两个字段。
特别地,在基础类型中,也添加 type 字段。于是基础类型,就演变成
template <int64_t N, int64_t D = 1>
struct number {using type = number<N, D>;using tag = number_tag;static const int64_t numer = impl::rat_reduce<(N == 0 || D == 0), N, D>::numer;static const int64_t denom = impl::rat_reduce<(N == 0 || D == 0), N, D>::denom;
};template <bool flag>
struct boolean {using tag = boolean_tag;using type = boolean<flag>;static const bool value = flag;
};
输入输出
模板元编程,可以使用模板参数作为输入。现在我们再编写一个 display
的输出函数。
template <typename T>
struct display_impl {static std::ostream &display(std::ostream &os, bool showBracket, number_tag) {...}static std::ostream &display(std::ostream &os, bool showBracket, symbol_tag) {...}static std::ostream &display(std::ostream &os, bool showBracket, boolean_tag) {...}static std::ostream &display(std::ostream &os, bool showBracket, null_tag) {...}static std::ostream &display(std::ostream &os, bool showBracket, pair_tag) {...}static std::ostream &display(std::ostream &os, bool showBracket) {return display(os, showBracket, typename T::tag());}
};template <typename T>
void display(std::ostream &os = std::cout) {display_impl<T>::display(os, true) << std::endl;
};
使用类型 tag
对函数进行分派。这也是模板元编程的一种常见方法。
using lst = list<symbol("*table*"),cons<symbol("a"), number<1>>,cons<symbol("b"), number<1>>>;
display<lst>();
输出
(*table* (a . 1) (b . 1))
结束
现在我们用 C++ 模板表达出,基础类型、组合类型、流程控制、输入输出。在上面的基础上,再编写一批基础函数,比如 add
、sub
、mul
、div
。也可以编写一些高阶函数,比如 filter
、map
。
实际上,我们已经用模板实现了 Scheme
的一个子集,只是语法不同。Scheme 的嵌套原括号(),变成了模板语法的嵌套尖括号<>。模板语法写起来更麻烦,更难以阅读,但思路是一样的。
之后再阅读 SICP 这本书,看看如何用最简单的 Scheme 特性写出复杂的程序。再用 C++ 模板语法进行一点改写。看起来就很厉害的样子了。
数据字典模板_C++ 模板元编程:一种屠龙之技相关推荐
- 浅谈 C++ 元编程
随着 C++ 11/14/17 标准的不断更新,C++ 语言得到了极大的完善和补充.元编程作为一种新兴的编程方式,受到了越来越多的广泛关注.结合已有文献和个人实践,对有关 C++ 元编程进行了系统的分 ...
- 模板元编程实现素数判定
模板元编程(英语:Template metaprogramming:缩写:TMP)是一种元编程技术,不夸张的说,这项技术开启了一种新的C++编程方式.编译器使用模板产生暂时性的源码,然后再和剩下的源码 ...
- 现代C++模板元编程基础
元函数的基础介绍 C++的模板元编程是函数式编程,所以函数是一等公民.一切在编译期间执行的函数都可以称为元函数.元函数有struct和constexpr两种定义方式,前者是一直使用的,后者是C++11 ...
- C++ 模板元编程简介
文章目录 1.概述 2.模板元编程的作用 3.模板元编程的组成要素 4.模板元编程的控制逻辑 4.1 if 判断 4.2 循环展开 4.3 switch/case 分支 5.特性.策略与标签 6.小结 ...
- 跟我学c++高级篇——模板元编程之十一鸭子类型
一.鸭子类型 鸭子类型不是从c++中出现的,duck typing这种称呼在Python中比较多见.那么什么是鸭子类型呢?它是动态类型的一种风格,只要是对象的特征(其方法和属性集)和某个类型一致,就认 ...
- C++ 模板元编程的应用有哪些,意义是什么?
https://www.cnblogs.com/liangliangh/p/4219879.html 为了谈应用,先谈谈使命.模板元编程的根在模板.模板的使命很简单:为自动代码生成提供方便.提高程序员 ...
- 闭关之 C++ 函数式编程笔记(四):monad 和 模板元编程
目录 第十章 monad 注意 10.1 仿函数并不是以前的仿函数 10.1.1 处理可选值 10.2 monad: 更强大的仿函数 10.3 基本的例子 10.4 range 与 monad 的嵌套 ...
- C++高阶必会操作--模板元编程
泛型编程大家应该都很熟悉了,主要就是利用模板实现"安全的宏",而模板元编程区别于我们所知道的泛型编程,它是一种较为复杂的模板,属于C++的高阶操作了,它最主要的优点就在于把计算过程 ...
- C++模板元编程详细教程(之五)
前序文章请看: C++模板元编程详细教程(之一) C++模板元编程详细教程(之二) C++模板元编程详细教程(之三) C++模板元编程详细教程(之四) STL中提供的工具 从这一篇开始,我们将正式介绍 ...
最新文章
- 接上一篇Ansible和celery的结合,在celery的tasks.py文件里为了实现并发不阻塞的需求,用到了多进程
- wxWidgets 示例演示 wxWrapSizer 的使用
- Class.forName()的作用与使用总结
- ActiveMQ 实现消息接收发送
- [转]MSBuild入门
- SpringAOP的SchemaBase方式
- 【汇编语言】除法(DIV/IDIV)
- android studio 导入依赖报错——Could not find com.xxx,(# fields: 66338 > 65536)
- 手指甲上的月牙辨健康,月牙会“丢”也能“长回来”
- string拼接_String拼接操作-的优化
- Spring Cloud 微服务下的权限解决方案
- python 日志输出变量_Python日志输出
- linux基础学习思维导图及文档(17万字)
- 截止失真放大电路_反馈/反馈电路/反馈类型的判别方法
- 机器学习:k-means聚类效果评估
- 什么是Web缓存控制(基于HTTP头域)
- 名帖288 董其昌 行书《岳阳楼记》
- 第二类曲面积分、场论、高斯公式和斯托克斯公式
- xp计算机用户名和密码忘记了怎么办,电脑的XP系统密码被不小心忘记了怎么办?...
- 5G 几年了,高铁信号怎么还是这么差?
热门文章
- 3D光场重建率先被中国团队推向零售商用!谷歌同款技术,哈佛高材生创办,高通投资认定...
- Android 内存优化- ANR 详解
- 那匆匆2014年,明明想静静
- CRF进行实体的识别
- Waymo在自动驾驶领域优势明显,但并非无懈可击
- 2021年R2移动式压力容器充装考试题及R2移动式压力容器充装考试试卷
- 【discuz x3】home_follow.php(广播页面)中$alist = getfollowfeed($vuid, $view, true, $start, $perpage);结果
- 小司机带你学习单例模式的六种姿势!
- 谷歌翻译网页无法翻译,最新解决方法
- 影评(一):《寄生虫》韩国(2019)