(二)模板元编程简介

从引入 template 关键字开始,C++里就出现了泛型编程,而又泛型编程衍生出的模板元编程(template meta_programming,简称“元编程”)则是众多编程范式中最复杂、最强大和最具有权威的一种。所谓“元编程”——metaprogramming,有着完全不同于普通程序的许多特点,是一种全新的编程体验。下面将介绍模板元编程的一些基础概念,它们是现代C++和boost程序库组件的基础。

1. 概述

元编程(meta-programming)也被称为“超程序”,“超编程”或“产生式编程”,这样说法一定程度上反映了其本质——它是一种位于普通程序之上、超越普通程序的程序,可以操纵、产生程序的程序。模板元编程本质上是泛型编程的一个子集,从广义上来说,所有使用template 的泛型代码都可以称作元程序——因为泛型编程代码并不是真正可编译执行的代码,它们只是定义了代码的产生规则,是用来生成代码的“模板”。然而模板元编程又不完全等同于泛型编程,它是一种“函数式编程”,是图灵完备的,可以“计算”任何东西。
模板元编程的允许是在编译期,它把编译器变成了元程序的解释器。

2. 语法元素

模板元编程产生的元程序是在编译期执行的程序,操作对象也不是普通的变量,因此不能使用运行时的C++ 关键字(if、else、for),可以的语法元素相当有限,最常用的包括:

  • enum、static 用来定义编译期的变量
  • typedef、using,最重要的元编程关键字,用于定义元数据
  • template,模板元编程的“起点”,主要用于定义元函数
  • “::”,域运算符,用于解析类型作用域获取计算结果(元数据)

3. 元数据

元编程可操作的数据就称为“元数据”(meta date),也就是C++ 编译器在编译期可操作的数据,它是模板元编程的基础。元数据都是不可变的,不能够就地修改,最常见的元数据是整数和C++ 类型(type)。这些元数据不是运行时的普通变量,而是如 int、double、class(非模板类)这样的抽象数据类型。要是对元数据再细分归类,则它又可分为:

  • 整数元数据
  • 值型元数据(int、double等POD值类型)
  • 函数元数据(函数类型)
  • 类元数据(class、struct等用户自定义类型)

对于下面所提到的‘元数据“,特征非整数类型的元数据。
使用tyoedef 关键字可以任意定义(声明)元数据,很像运行时的变量定义,如:

typedef int mtes_data1;   //元数据meta_data1, 值为 int
typedef std::vector<float> meta_data2; //元数据meta_data2,值为vector<float>

使用using 也可以达到同样的效果
using meta_data1 = int;
using meta_data2 = std:;vector<float>;

4. 元函数

元函数(meta function)是模板元编程中用于操作处理元数据的”构件“,可以在编译期被”调用“,因为其功能和形式类似运行时的函数而得名,是元编程里的核心概念。它实际上是一个类或者模板类,通常形式为:

template<typename arg1, typename arg2, ...>  //元函数参数列表
struct meta_function  //元函数名
{
	typedef sone-define type;  //元函数返回元数据
	//using type = some-define; 

	static int const val = some-int;  //元函数返回的整数
};//结束

编写元函数就像是编写一个普通的运行是函数,但形式上却是一个模板类:

  • 函数参数列表的园括号”()“ 变成了模板列表的尖括号”<>“
  • 函数的形参变成了模板参数(即元数据),并且要使用关键字 typedef 修饰
  • 因为不能使用运行时关键字,所以元函数不能像其他普通函数那样使用return 返回计算结果,而是需要在内部使用typedef / using 定义一个名为 type 的类型(元数据)或者名为val 的值作为返回
  • 最后以分号结束,因为它本质上是一个类

元函数也可以没有返回值(即不定义内部类型type),也可以有重载(模板特例/偏特化),也可以有缺省参数,也可以分为无参、单参、多参、可变参等类别。但元函数没有普通函数参数传值、传引用的区别,也没有函数指针的概念。如果有必要,元函数可以使用 typedef / using 关键字 “返回” 任意多个返回值,并且这些没有顺序关系,能够用 ”::“ 来任意获取。为表述方便,下面将只返回 ::type 的元函数称为标准元函数,而返回多个元数据的元函数称为非标准元函数。
下面给一段值元函数的代码:

template<int N, int M>  //两个元数据
struct meta_func
{
	static const int val = N + M; //编译期计算整数之和
};

cout << meta_func<10, 10>::val << endl; //计算结果 20

这里需要主要的是meta_func 的执行过程,它的计算在编译期的时候就已经完成了(即模板实例化),meta_func::val 实际上是一个编译期常量,程序运行时不会有任何计算动作而是直接使用结果。如果这是一个大型的元函数,那么在编译期节约的计算量就会相当可观,可以显著提高程序运行的效率。
由于元函数的计算发生在编译期,所以下列代码不能成立:(不能使用运行时的变量)
在这里插入图片描述
下面示范了另一个元函数,它返回元函数参数列表中的第一个元数据:

template<typename T1, typename T2>  //两个形参
struct select1st
{
	typedef T1 type;  //返回T1,等价于using type = T1
};

5. 元函数转发

元函数转发是模板元编程中一个经常用到的惯用法,相当于运行时的函数转发调用,但在模板元编程中则要用 public 继承实现,模板参数传递给父类完成元函数的 ”调用“,这样的子类会自动获得父类的::type 定义,同时也完成了元函数的返回。例如,下面代码把元函数数据调换位置后,转发给之前定义的元函数 select1st ,相当于select2nd 的功能:

template<typename T1, typename T2>
struct forword: select1st:  //元函数转发,默认是public继承
	select1st<T2, T1>  //参数位置变动
{};


template<typename T1, typename T2>
struct forward  //不用转发
{
	typedef typename select1st<T2, T1>::type type; //调用元函数计算
};

易知,元函数转发因为使用了类继承所以更加简洁

6. 工具宏

模板元编程是一种全新的C++ 编程范式,但仍然使用原有的语法,通篇的 typedef / using 、 template 关键字使元程序不易理解,所有完美可以定义一些工具宏,均以“mp_”开头(或者用增加习惯的方法定义),这样能够便于我们理解。

#define mp_arglist template //元函数参数列表
#define mp_arg    typename  //元函数参数声明
#define mp_function struct //元函数定义
#define mp_data  typedef //元数据定义

#define mp_return(T) mp_data T type  //元函数返回
//using type = T
#define mp_exec(Func)  Func::type   //获取元函数返回结果
#define eval(Func)   Func::value   //获取元函数返回值

这些分别把 template 、typename、struct 和 typedef 这四个模板元编程中最常用的关键字进行了重命名。

  • mp_arglist 表示元函数的参数列表开始
  • mp_arg 表示元函数的参数
  • mp_function 表示定义一个元函数
  • mp_data 表示定义一个元数据
  • mp_return / mp_eval / mp_exec 定义了元编程中约定返回值用法,较原写法更清楚
mp_data int meta_data1;   //元数据meta_data1,值为int

mp_arglist<mp_arg T1, mp_arg T2>    //元函数的参数是T1、T2
mp_function select1st  //元函数select1st
{
	mp_return(T1);  //返回T1  -> type
}

很明显,使用工具宏使元程序看起来更加清楚,易于区分。但是由于宏预处理机制自身的“缺陷”,后三个工具宏的作用有限,它们只能处理简单的参数,如果带有 “,”,那么模板类就会失效,但Boost 库里面的 BOOST_IDENTITY_TYPE 来解决。

7. 应用示例

下面通过两个例子来示范元编程的基本使用:

  • 编译期比较大小
mp_arglist<int L, int R>
mp_function static_min  //元函数 static_min
{
	static const int value = (L < R) ? L : R;
};

assert((static_min<10, 20>::value == 10)); //编译期比较
  • demo_func 输入元数据 T 是指针类型返回const T,否则 const T*
mp_arglist<mp_arg T>   //单参元函数
mp_function demo_func
{
	mp_return(const T*);   //通常情况返回const T*
};

mp_arglist<mp_arg T>
mp_function demo_func<T*>   //对T*情况进行模板实例化
{
	mp_return(const T);
};

//这里用is_same元函数进行验证 #include<boost/type_traits/is_same>
assert((is_same<mp_exec(demo_func<int>), const int*>::value));
assert((is_same<mp_exec(demo_func<int*>), const int>::value));
//这里的 assert 必须用两对括号来包围断言

8. 总结

我们介绍了模板元编程的基础知识,包括元编程 / 元程序 / 元数据 和 元函数转发等概念。

  • 元编程是一种超越普通程序的程序,在C++中元编程是使用模板技术实现的,所以它右被称为模板元编程。元程序可以由C++编译期解释执行,把部分计算量由运行时转移到编译时完成,提高程序运行效率,但元编程更大的用途是类型推导,操作C++类型体系。
  • 元数据是元编程的操作对象,可以是整数(含bool)或任意的C++类型
  • 元函数是元编程的核心,它表现为C++的一个模板类,我们必须使用元函数才能操作元数据。它以内部定义::type 或 ::value 返回计算结果,并可以使用public 继承的方式实现元函数转发

至此结束

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leisure-pp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值