什么是 模板元编程(Template Metaprogramming, TMP)?
模板元编程是利用 C++ 模板的编译期计算能力,在编译器编译代码时完成逻辑运算、类型推导、常量计算的编程范式 —— 简单说,就是 “让编译器帮你写代码 / 算结果”。
一、先搞懂:模板元编程的核心本质
常规 C++ 代码是 “运行期执行”(比如int a=1+2;在程序运行时计算),而模板元编程是 “编译期执行”—— 编译器在处理模板时,会像 “解释器” 一样,执行你写的模板逻辑,最终生成一个 “确定的结果 / 类型”,运行期只需要直接用这个结果。
二、模板元编程的核心特点(和常规编程对比)
模板元编程和常规编程有几大核心区别:

三、模板元编程的例子
在模板元编程的学习中,有几个例子帮助我们理解学习。
1、基础 —— 编译期计算整数加法
目标:理解「模板递归」和「编译期常量」的基础用法,实现两个整数的编译期加法。
//1.主模板,定义编译期加法逻辑
template<int A, int B>
struct CompileTimeAdd {
//static const:声明编译期常量
//核心逻辑:A+B 拆分为(A-1)+(B+1),递归直到A=0
static const int value = CompileTimeAdd<A - 1, B + 1>::value;
};
//2.模板特化,递归终止条件(当A=0时,直接返回B)
template<int B>
struct CompileTimeAdd<0, B> {
static const int value = B;
};
int main() {
// 关键:所有计算在编译期完成,运行期仅读取常量
cout << "3 + 5 = " << CompileTimeAdd<3, 5>::value << endl; // 输出8
cout << "10 + 20 = " << CompileTimeAdd<10, 20>::value << endl; // 输出30
return 0;
}
这个是通过模板递归和模板特化,设置合理的终止条件,可以在编译期就获取结果。
2、进阶 —— 编译期判断类型
目标 :理解「类型模板」和「编译期类型判断」,实现:判断一个类型是否是指针类型。
template<class T>
struct IsPoint {
static const bool value = false;
};
//偏特化判断是否是指针
template<class T>
struct IsPoint<T*> {
static const bool value = true;
};
int main() {
int* a = NULL, b = 0;
//cout << IsPoint<typeid(a)> ::value << endl;
/*错误:
* typeid(a)的本质
typeid(a)是 C++ 的运行时类型运算符,返回的是:
类型:const std::type_info&(一个类的引用,不是 “类型名”,也不是 “常量值”);
时机:结果在运行时确定(哪怕是非多态类型,typeid的返回值也是运行时对象)。
*/
cout << IsPoint<decltype(a)> ::value << endl;
cout << IsPoint<decltype(b)> ::value << endl;
//这里用decltype合适,decltype(a):编译期运算符,返回a的静态类型
}
这里通过模板的特性,可以使我们在编译时直接获取数据的类型是否是指针,这是简单的类型萃取过程。
3.实战 —— 编译期数组求和(结合递归 + 类型)
目标:综合运用模板递归和类型,实现「编译期计算固定长度数组的和」。
constexpr int arr[] = { 1, 2, 3, 4, 5 };
// 辅助模板:获取数组长度(编译期)
template<class T, int N>
struct getlen {
static const int len = N;
};
// 1. 主模板:数组求和的递归逻辑
// 参数:数组类型T,数组长度N,当前索引Index
template<class T, int N, int index = 0>
struct ArrSum {
static const T value = (index < N) ? (arr[index] + ArrSum<T, N, index + 1>::value) : 0;
};
// 2. 终止条件:索引等于数组长度时,返回0
template<class T, int N>
struct ArrSum <T, N, N> {
static const T value = 0;
};
// 获取数组长度(编译期)
const int LEN = getlen<int, sizeof(arr) / sizeof(int)>::len;
int main() {
// 编译期计算数组和:1+2+3+4+5=15
cout << "数组和:" << ArrSum<int, LEN>::value << endl;
return 0;
}
这个就是实际的应用,帮助我们加深印象。
4. 现代 TMP——C++11 constexpr 简化写法
目标:理解 C++11 后「更易读的 TMP 写法」,用constexpr替代传统结构体模板。
// 1. constexpr函数:编译期阶乘(替代结构体递归)
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// 2. constexpr函数:编译期判断是否是偶数
constexpr bool IsEven(int n) {
if (n % 2 == 0)return true;
else return false;
}
int main() {
// 编译期计算:5! = 120
constexpr int fact5 = factorial(5);
cout << "5! = " << fact5 << endl;
// 编译期判断:6是否是偶数
constexpr bool even6 = isEven(6);
cout << "6是否是偶数:" << boolalpha << even6 << endl; // boolalpha让输出为true/false
// 编译期计算:10! = 3628800
constexpr int fact10 = factorial(10);
cout << "10! = " << fact10 << endl;
return 0;
}
C++11之后,出现更加易读的tmp写法,用 constexpr 替代传统结构体模板。
constexpr 函数:被constexpr修饰的函数,满足条件时(无副作用、递归深度有限)可在编译期执行;
这里顺便提一下const和constexpr的区别。
const 的核心是“只读”,也就是(运行时 / 编译期都能使用,仅保证变量值不可修改)。
constexpr的核心是 “编译期常量”(必须在编译期确定值,可用于模板参数、数组大小等编译期上下文)
这里写个代码帮助加深印象:
关键区别 1:修饰变量
1. const:“只读变量”(不一定是编译期常量)
#include <iostream>
using namespace std;
int main() {
int a = 10; // 运行时变量
const int b = a; // b是只读变量,值在运行时确定(a的值)
// 错误:b的值是运行时确定的,不能作为数组大小(编译期上下文)
// int arr[b] = {1,2,3};
b = 20; // 编译报错:const变量只读,不能修改
return 0;
}
只有当const变量用编译期常量初始化时,它才 “偶然” 成为编译期常量:
const int c = 10; // c是const,且用字面量初始化→编译期常量
int arr[c] = {1,2,3}; // 合法:c的值编译期确定
2. constexpr:“编译期常量”(强制编译期确定值)
constexpr修饰的变量 / 函数,必须在编译期计算出值,否则直接编译报错:
int main() {
int a = 10;
// 错误:a是运行时变量,无法在编译期确定constexpr变量的值
// constexpr int b = a;
constexpr int c = 10; // 合法:字面量初始化,编译期确定
int arr[c] = {1,2,3}; // 合法:编译期上下文
// 进阶:constexpr函数(编译期计算)
constexpr int add(int x, int y) { return x + y; }
constexpr int d = add(3,4); // d=7,编译期计算完成
return 0;
}
关键区别 2:修饰函数(C++11+)
const不能修饰函数(仅能修饰成员函数的this指针),而constexpr可以修饰函数,使其成为 “编译期函数”—— 满足条件时,函数会在编译期执行,返回编译期常量。
constexpr函数的要求(C++11)
- 函数体只能有一条
return语句(C++14 放宽); - 返回值和参数类型必须是 “字面量类型”(int、double、指针等);
- 调用时必须传入编译期常量参数,才能在编译期计算。
关键区别 3:修饰类 / 构造函数(C++11+)
constexpr可以修饰构造函数,让类对象成为 “编译期常量对象”;而const仅能修饰类对象(使其成为只读对象)。
下一章讲一下类型萃取编程。

1万+

被折叠的 条评论
为什么被折叠?



