模板元编程

元程序是在编译期由编译器直接解析并执行的。

在编译期,编译器只能做整数值计算和类型计算,这就导致了元程序的代码结构和我们熟知的代码结构(运行时代码结构)有很大区别。

  • 编译期计算:所有计算在编译时完成,运行时无额外开销。
  • 递归实现逻辑:由于模板缺乏变量和循环,依赖递归和模板特化进行计算。
  • 类型萃取:可用于类型推导、类型转换及静态检查。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<int N>
struct Fibonacci {
static constexpr int num = Fibonacci<N - 1>::num + Fibonacci<N - 2>::num;
};

template<>
struct Fibonacci<0> {
static constexpr int num = 0;
};

template<>
struct Fibonacci<1> {
static constexpr int num = 1;
};

查看汇编,可以看到值已经求出来:

image20250316131356284.png

这是一道利用模板元编程编译期计算出斐波那契数的例子,解读如下:

  • 在模板元编程中,我们通常使用 struct 而不是 class。但是并没有硬性规定不能用 class,只要手动加上 publicclass 也能实现相同的功能
  • 变量是静态的(static),方便后面能够通过::访问到,如:Fibonacci<N - 1>::num
  • 得加上 constexpr 或 const 属性才能让你编译期间计算,但建议统一使用 constexpr
  • 由于模板缺乏变量和循环,依赖递归和模板特化进行计算。既然是递归,那就得有终止条件,因此得全特化原模板,实现终止条件

 

我们也可以正常写 斐波那契函数,然后用 constexpr 修饰函数返回值,达到编译期计算的目的:

1
2
3
4
5
constexpr int fn(int N) {
if (N == 0) return 0;
if (N == 1) return 1;
return fn(N - 1) + fn(N - 2);
}

但如果你是在 C++11 这样写就会报错(C++11 constexpr 不允许递归),但是 C++14 往上没有问题,随着版本的提高,constexpr 的能力或者自由会越多。

image20250316133054394.png

 

C++17 将 constexpr 这个关键字引入到 if 语句中,允许在代码中声明常量表达式的判断条件:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <type_traits>

template<typename T>
auto print_type_info(const T& t) {
if constexpr (std::is_integral<T>::value) { // 编译期判断是否是整数
return t + 1;
} else {
return t + 0.001;
}
}

如果你按照下面的方式调用:

1
2
3
4
int main() {
std::cout << print_type_info(5) << std::endl; // 整数,返回 6
std::cout << print_type_info(3.14) << std::endl; // 浮点数,返回 3.141
}

编译期间,代码表现为:

1
2
3
4
5
6
7
8
9
10
int print_type_info(const int& t) {
return t + 1;
}
double print_type_info(const double& t) {
return t + 0.001;
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}

if constexprC++17 新增的 编译期分支判断,其主要作用是:

  1. 在编译期决定是否编译某个代码分支
  2. 不会编译无效的分支,避免编译错误。

如果所有分支都走不通的话,就会报错,不会编译通过。

 

constexpr 结合 std::array(编译期缓存):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
constexpr std::array<int, 11> fibonacci_table = [] {
std::array<int, 11> arr{};
arr[0] = 0;
arr[1] = 1;
for (int i = 2; i < 11; ++i) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr;
}();

int main() {
constexpr int num = fibonacci_table[10]; // 编译期查表
std::cout << num << std::endl;
return 0;
}

参考内容

C++模板元编程