请选择 进入手机版 | 继续访问电脑版

C++模板元编程(C++ template metaprogramming)(1)

发表于 2022-12-23 17:11:25 显示全部楼层 0 1081

实验平台:Win7,VS2013 Community,GCC 4.8.3(在线版)

所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得异常灵活,能实现很多高级动态语言才有的特性(语法上可能比较丑陋,一些历史原因见下文)。
普通用户对 C++ 模板的使用可能不是很频繁,大致限于泛型编程,但一些系统级的代码,尤其是对通用性、性能要求极高的基础库(如 STL、Boost)几乎不可避免的都大量地使用 C++ 模板,
一个稍有规模的大量使用模板的程序,不可避免的要涉及元编程(如类型计算)。本文就是要剖析 C++ 模板元编程的机制。

下面所给的所有代码,想做实验又懒得打开编译工具?一个在线运行 C++ 代码的网站(GCC 4.8)很好~  


1. C++模板的语法
函数模板(function template)和类模板(class template)的简单示例如下:
  1. #include <iostream>

  2. // 函数模板
  3. template<typename T>
  4. bool equivalent(const T& a, const T& b) {
  5.        return !(a < b) && !(b < a);
  6. }
  7. // 类模板
  8. template<typename T=int> // 默认参数
  9. class bignumber{
  10.       T _v;
  11. public:
  12.       bignumber(T a) : _v(a) { }
  13.       inline bool operator<(const bignumber& b) const; // 等价于 (const bignumber<T>& b)
  14. };
  15. // 在类模板外实现成员函数
  16. template<typename T>
  17. bool bignumber<T>::operator<(const bignumber& b) const{
  18.     return _v < b._v;
  19. }

  20. int main()
  21. {
  22.     bignumber<> a(1), b(1); // 使用默认参数,"<>"不能省略
  23.     std::cout << equivalent(a, b) << '\n'; // 函数模板参数自动推导
  24.     std::cout << equivalent<double>(1, 2) << '\n';
  25.     std::cin.get();    return 0;
  26. }
复制代码
程序输出如下:

  1. 1
  2. 0
复制代码
关于模板(函数模板、类模板)的模板参数(详见文献[1]第3章):

  • 类型参数(type template parameter),用 typename 或 class 标记;
  • 非类型参数(non-type template parameter)可以是:整数及枚举类型、对象或函数的指针、对象或函数的引用、对象的成员指针,非类型参数是模板实例的常量;
  • 模板型参数(template template parameter),如“template<typename T, template<typename> class A> someclass {};”;
  • 模板参数可以有默认值(函数模板参数默认是从 C++11 开始支持);
  • 函数模板的和函数参数类型有关的模板参数可以自动推导,类模板参数不存在推导机制;
  • C++11 引入变长模板参数,请见下文。



模板特例化(template specialization,又称特例、特化)的简单示例如下:
  1. // 实现一个向量类
  2. template<typename T, int N>
  3. class Vec{
  4.     T _v[N];
  5.     // ... // 模板通例(primary template),具体实现
  6. };
  7. template<>
  8. class Vec<float, 4>{
  9.     float _v[4];
  10.     // ... // 对 Vec<float, 4> 进行专门实现,如利用向量指令进行加速
  11. };
  12. template<int N>
  13. class Vec<bool, N>{
  14.     char _v[(N+sizeof(char)-1)/sizeof(char)];
  15.     // ... // 对 Vec<bool, N> 进行专门实现,如用一个比特位表示一个bool
  16. };
复制代码
所谓模板特例化即对于通例中的某种或某些情况做单独专门实现,最简单的情况是对每个模板参数指定一个具体值,这成为完全特例化(full specialization),另外,可以限制模板参数在一个范围取值或满足一定关系等,这称为部分特例化(partial specialization),用数学上集合的概念,通例模板参数所有可取的值组合构成全集U,完全特例化对U中某个元素进行专门定义,部分特例化对U的某个真子集进行专门定义。

更多模板特例化的例子如下(参考了文献[1]第44页):

  1. template<typename T, int i> class cp00; // 用于模板型模板参数
  2. // 通例
  3. template<typename T1, typename T2, int i, template<typename, int> class CP>
  4. class TMP;
  5. // 完全特例化
  6. template<>
  7. class TMP<int, float, 2, cp00>;
  8. // 第一个参数有const修饰
  9. template<typename T1, typename T2, int i, template<typename, int> class CP>
  10. class TMP<const T1, T2, i, CP>;
  11. // 第一二个参数为cp00的实例且满足一定关系,第四个参数为cp00
  12. template<typename T, int i>
  13. class TMP<cp00<T, i>, cp00<T, i+10>, i, cp00>;
  14. // 编译错误!,第四个参数类型和通例类型不一致
  15. //template<template<int i> CP>
  16. //class TMP<int, float, 10, CP>;
复制代码
关于模板特例化(详见文献[1]第4章):

  • 在定义模板特例之前必须已经有模板通例(primary template)的声明;
  • 模板特例并不要求一定与通例有相同的接口,但为了方便使用(体会特例的语义)一般都相同;
  • 匹配规则,在模板实例化时如果有模板通例、特例加起来多个模板版本可以匹配,则依据如下规则:对版本AB,如果 A 的模板参数取值集合是B的真子集,则优先匹配 A,如果 AB 的模板参数取值集合是“交叉”关系(AB 交集不为空,且不为包含关系),则发生编译错误,对于函数模板,用函数重载分辨(overload resolution)规则和上述规则结合并优先匹配非模板函数。

对模板的多个实例,类型等价(type equivalence)判断规则(详见文献[2] 13.2.4):同一个模板(模板名及其参数类型列表构成的模板签名(template signature)相同,函数模板可以重载,类模板不存在重载)且指定的模板实参等价(类型参数是等价类型,非类型参数值相同)。
如下例子:


  1. #include <iostream>
  2. // 识别两个类型是否相同,提前进入模板元编程^_^
  3. template<typename T1, typename T2> // 通例,返回 false
  4. class theSameType       { public: enum { ret = false }; };
  5. template<typename T>               // 特例,两类型相同时返回 true
  6. class theSameType<T, T> { public: enum { ret = true }; };

  7. template<typename T, int i> class aTMP { };

  8. int main(){
  9.     typedef unsigned int uint; // typedef 定义类型别名而不是引入新类型
  10.     typedef uint uint2;
  11.     std::cout << theSameType<unsigned, uint2>::ret << '\n';
  12.     // 感谢 C++11,连续角括号“>>”不会被当做流输入符号而编译错误
  13.     std::cout << theSameType<aTMP<unsigned, 2>, aTMP<uint2, 2>>::ret << '\n';
  14.     std::cout << theSameType<aTMP<int, 2>, aTMP<int, 3>>::ret << '\n';
  15.     std::cin.get(); return 0;
  16. }
复制代码
  1. 1
  2. 1
  3. 0
复制代码


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

shop_da_admin

管理员

39

主题

40

帖子

247

积分
Ta的主页 发消息

网友分享更多 >

  • 机器学习的统计学知识
  • 漳州盛泰水产
  • 玉川茶家