编译期阶乘需用 struct 模板递归加完全特化实现,如 template<int N> struct Factorial 配合 template<> struct Factorial<0> 终止;推荐 static constexpr int value = ...,避免负数参数,注意递归深度与溢出限制。

用 struct 模板递归 + 特化实现编译期阶乘
核心就是让编译器在实例化模板时“展开”整个计算过程,而不是等到运行时才调用函数。关键不是写个能算的函数,而是构造一个类型,它的静态成员 value 在编译期就确定了。
常见错误是只写递归模板,不加终止特化——编译器会无限展开,最终报错 template instantiation depth exceeds maximum 或直接卡死。
-
template<int N> struct Factorial定义递归主体,Factorial<N>::value依赖Factorial<N-1>::value - 必须提供
template<> struct Factorial<0>(或<1>,取决于定义)作为完全特化,否则无终止条件 - 参数类型建议用
unsigned int或size_t,避免负数导致非法实例化(编译失败而非静默错误) - C++11 起推荐用
static constexpr int value = ...,比老式enum { value = ... }更类型安全、支持取地址和模板参数推导
constexpr 函数比模板更直观,但要注意调用上下文
从 C++11 开始,constexpr 函数能在编译期求值,写法更接近普通函数,可读性高。但它不是“永远编译期执行”——只有当所有实参都是字面量常量且满足约束时,才会触发编译期计算。
典型陷阱:把变量传给 constexpr 函数,结果退化为运行时调用,失去零成本优势。
立即学习“C++免费学习笔记(深入)”;
- 正确用法:
static_assert(factorial(5) == 120);—— 编译器强制在编译期求值并校验 - 错误用法:
int n = 5; auto x = factorial(n);—— 此时是运行时调用,和普通函数无异 - 若需泛型支持(比如模板参数是类型或非类型模板参数),
constexpr函数仍需配合模板使用,不能单独替代模板元编程逻辑
模板阶乘的值怎么用?别直接取 Factorial<N>::value 就完事
编译期常量要真正“零开销”,得确保它被用在需要常量表达式的上下文中,否则可能被降级为运行时变量初始化。
容易忽略的是:即使 value 是 constexpr,如果用在非 constexpr 上下文(比如普通函数体中赋值给非 const 变量),编译器不会报错,但也就失去了编译期优势。
- 安全用法:
std::array<int, Factorial<4>::value> arr;—— 数组大小必须是常量表达式 - 安全用法:
static_assert(Factorial<3>::value == 6); - 危险用法:
int x = Factorial<3>::value;—— 看似没问题,但若后续优化关闭或上下文变化,可能引入隐式运行时初始化 - 更稳妥写法:
constexpr int v = Factorial<3>::value;,明确要求编译期绑定
模板递归深度和编译时间容易失控
阶乘本身增长极快,Factorial<20>::value 已超 int 范围;更大的 N 不仅溢出,还会显著拖慢编译速度——因为每个中间实例(Factorial<19>, Factorial<18>…)都要生成符号、检查、优化。
这不是运行时栈溢出,而是编译器内存占用和模板实例化爆炸。很多项目默认限制递归深度(如 GCC 的 -ftemplate-depth=),超限直接报错。
- 实用上限建议:整数阶乘控制在
N <= 10(int安全),N <= 20需用unsigned long long - 若真需要大数阶乘表,改用预生成头文件或构建脚本生成 constexpr 数组,避开模板递归
- C++20 起可用
consteval强制编译期求值,但对递归深度限制更严,不适合深嵌套场景



















