浅谈C++元编程

随着 C++ 11/14/17 标准的不断更新,C++ 语言得到了极大的完善和补充。元编程作为一种新兴的编程方式,受到了越来越多的广泛关注。结合已有文献和个人实践,对有关 C++ 元编程进行了系统的分析。首先介绍了 C++ 元编程中的相关概念和背景,然后利用科学的方法分析了元编程的 演算规则、基本应用 和实践过程中的 主要难点,最后提出了对 C++ 元编程发展的 展望。

1 引言
1.1 什么是元编程
元编程 (metaprogramming) 通过操作 程序实体 (program entity),在 编译时 (compile time) 计算出 运行时 (runtime) 需要的常数、类型、代码的方法。

一般的编程是通过直接编写 程序 (program),通过编译器 编译 (compile),产生目标代码,并用于 运行时 执行。与普通的编程不同,元编程则是借助语言提供的 模板 (template) 机制,通过编译器 推导 (deduce),在 编译时 生成程序。元编程经过编译器推导得到的程序,再进一步通过编译器编译,产生最终的目标代码。在 § 2.1.3 中,用一个例子说明了两者的区别。

因此,元编程又被成为 两级编程 (two-level programming),生成式编程 (generative programming) 或 模板元编程 (template metaprogramming)。[1]

1.2 元编程在 C++ 中的位置
C++ 语言 = C 语言的超集 + 抽象机制 + 标准库
C++ 的 抽象机制 (abstraction mechanisms) 主要有两种:面向对象编程 (object-oriented programming) 和 模板编程 (generic programming)。[1]

为了实现面向对象编程,C++ 提供了 类 (class),用 C++ 的已有 类型 (type) 构造出新的类型。而在模板编程方面,C++ 提供了 模板 (template),以一种直观的方式表示 通用概念 (general concept)。

模板编程的应用主要有两种:泛型编程 (generic programming) 和 元编程 (meta-programming)。前者注重于 通用概念 的抽象,设计通用的 类型 或 算法 (algorithm),不需要过于关心编译器如何生成具体的代码;而后者注重于设计模板推导时的 选择 (selection) 和 迭代 (iteration),通过模板技巧设计程序。[1]

1.3 C++ 元编程的历史
1988 年,David R. Musser 和 Alexander A. Stepanov 提出了 模板 [2],并最早应用于 C++ 语言。Alexander A. Stepanov 等人在 Bjarne Stroustrup 的邀请下,参与了 C++ 标准模板库 (C++ Standard Template Library, C++ STL) (属于 C++ 标准库 的一部分) 的设计。[3] 模板的设计初衷仅是用于泛型编程,对数据结构和算法进行 抽象 (abstraction)。

而在现代 C++ 的时代,人们发现模板可以用于元编程。1994 年的 C++ 标准委员会会议上,Erwin Unruh 演示了一段利用编译器错误信息计算素数的代码。[4] 1995 年的 Todd Veldhuizen 在 C++ Report 上,首次提出了 C++ 模板元编程 的概念,并指出了其在数值计算上的应用前景。[5] 随后,Andrei Alexandrescu 提出了除了数值计算之外的元编程应用,并设计了一个通用的 C++ 的模板元编程库 —— Loki。[6] 受限于 C++ 对模板本身的限制,Andrei Alexandrescu 等人又发明了 D 语言,把元编程提升为语言自身的一个特性。[7]

元编程已被广泛的应用于现代 C++ 的程序设计中。由于元编程不同于一般的编程,在程序设计上更具有挑战性,所以受到了许多学者和工程师的广泛关注。

1.4 元编程的语言支持
C++ 的元编程主要依赖于语言提供的模板机制。除了模板,现代 C++ 还允许使用 constexpr 函数进行常量计算。[8] 由于 constexpr 函数功能有限,所以目前的元编程程序主要基于模板。这一部分主要总结 C++ 模板机制相关的语言基础,包括 狭义的模板 和 泛型 lambda 表达式。

1.4.1 狭义的模板
目前最新的 C++ 将模板分成了 4 类:类模板 (class template),函数模板 (function template),别名模板 (alias template) 和 变量模板 (variable template)。[9] 前两者能产生新的类型,属于 类型构造器 (type constructor);而后两者仅是语言提供的简化记法,属于 语法糖 (syntactic sugar)。

类模板 和 函数模板 分别用于定义具有相似功能的 类 和 函数 (function),是泛型中对 类型 和 算法 的抽象。在标准库中,容器 (container) 和 函数 都是 类模板 和 函数模板 的应用。

别名模板 和 变量模板 分别在 C++ 11 和 C++ 14 引入,分别提供了具有模板特性的 类型别名 (type alias) 和 常量 (constant) 的简记方法。前者 类模板的嵌套类 等方法实现,后者则可以通过 constexpr 函数、类模板的静态成员、函数模板的返回值 等方法实现。例如,C++ 14 中的 别名模板 std::enable_if_t 等价于 typename std::enable_if::type,C++ 17 中的 变量模板 std::is_same<T, U> 等价于 std::is_same<T, U>::value。尽管这两类模板不是必须的,但一方面可以增加程序的可读性(§ 4.1),另一方面可以提高模板的编译性能(§ 4.4)。

C++ 中的 模板参数 (template parameter / argument) 可以分为三种:值参数,类型参数,模板参数。[10] 从 C++ 11 开始,C++ 支持了 变长模板 (variadic template):模板参数的个数可以不确定,变长参数折叠为一个 参数包 (parameter pack) [11],使用时通过编译时迭代,遍历各个参数(§ 2.2.2)。标准库中的 元组 (tuple) —— std::tuple 就是变长模板的一个应用(元组的 类型参数 是不定长的,可以用 template<typename… Ts> 匹配)。

尽管 模板参数 也可以当作一般的 类型参数 进行传递(模板也是一个类型),但之所以单独提出来,是因为它可以实现对传入模板的参数匹配。§ 3.2 的例子(代码 8)使用 std::tuple 作为参数,然后通过匹配的方法,提取 std::tuple 内部的变长参数。

特化 (specialization) 类似于函数的 重载 (overload),即给出 全部模板参数取值(完全特化)或 部分模板参数取值(部分特化)的模板实现。实例化 (instantiation) 类似于函数的 绑定 (binding),是编译器根据参数的个数和类型,判断使用哪个重载的过程。由于函数和模板的重载具有相似性,所以他们的参数 重载规则 (overloading rule) 也是相似的。

1.4.2 泛型 lambda 表达式
由于 C++ 不允许在函数内定义模板,有时候为了实现函数内的局部特殊功能,需要在函数外专门定义一个模板。一方面,这导致了代码结构松散,不易于维护;另一方面,使用模板时,需要传递特定的 上下文 (context),不易于复用。(类似于 C 语言里的回调机制,不能在函数内定义回调函数,需要通过参数传递上下文。)

为此,C++ 14 引入了 泛型 lambda 表达式 (generic lambda expression) [12]:一方面,能像 C++ 11 引入的 lambda 表达式一样,在函数内构造 闭包 (closure),避免在 函数外定义 函数内使用 的局部功能;另一方面,能实现 函数模板 的功能,允许传递任意类型的参数。

2 元编程的基本演算
C++ 的模板机制仅仅提供了 纯函数 (pure functional) 的方法,即不支持变量,且所有的推导必须在编译时完成。但是 C++ 中提供的模板是 图灵完备 (turing complete) 的 [13],所以可以使用模板实现完整的元编程。

元编程的基本 演算规则 (calculus rule) 有两种:编译时测试 (compile-time test) 和 编译时迭代 (compile-time iteration) [1],分别实现了 控制结构 (control structure) 中的 选择 (selection) 和 迭代 (iteration)。基于这两种基本的演算方法,可以完成更复杂的演算。

2.1 编译时测试
编译时测试 相当于面向过程编程中的 选择语句 (selec

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值