【c++】模板元编程

什么是 模板元编程(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仅能修饰类对象(使其成为只读对象)。

下一章讲一下类型萃取编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值