Appearance
constexpr 元编程:编译期计算与 if constexpr
一、constexpr:把值/函数求值提到编译期
- C++11
constexpr受限于很窄(单 return、无循环)。 - C++14 放宽:constexpr 函数里可有循环、局部变量、多条语句。
- C++17 泛化到能在数组大小/模板实参/static_assert 里用;C++20
consteval(强制编译期)、constinit(静态初始化)。
cpp
constexpr int pow(int b, int e) {
int r = 1;
for (int i=0;i<e;i++) r*=b; // C++14 起允许
return r;
}
constexpr int v = pow(2,10); // =1024 编译期算好规则:constexpr 函数"能编译期求就编译期求",传给非编译期实参时退化为运行期普通函数(除非 consteval 强制)。
二、编译器角度:何时真在编译期
- 需要常量表达式的地方(模板实参、数组边界、
static_assert、switch case)必须 constexpr + 可常量求值。 - 只声明
constexpr不等于一定编译期算—要看上下文是否要求常量语境。 - constexpr 化简后无运行时开销(若真在编译期)——HPC 用来把常量表、维度查表、哈希(如 FNV 常量哈希)在启动前算好。
三、经典编译期递归示例(模板元编程之魂)
早期没有 constexpr 时,只能用递归实例化算常量:
cpp
// 模板计算阶乘:编译期递归(现代用 constexpr 更清爽)
template <int N> struct Fact { static constexpr int value = N * Fact<N-1>::value; };
template <> struct Fact<0> { static constexpr int value = 1; };
// Fact<5>::value == 120 (编译期)
// 等价的 constexpr 写法:
constexpr int fact(int n){ return n <=1 ? 1 : n*fact(n-1); } // C++11类型层面的“运算”(类型变换)永远只能靠模板递归(因为没有"运行期类型")——这仍是元编程主体(编译期类型列表、Tuple/N 型计数器等)。
四、if constexpr(C++17):在模板内做编译期条件的正确姿势
cpp
template <typename T>
void print_thing(const T& v) {
if constexpr (std::is_pointer_v<T>) {
std::cout << *v; // 只有 T 是指针时这段被编译
} else {
std::cout << v; // 否则这段
}
}- 不满足的分支不实例化(否则(对 int)
*v会编译错)。 - 用于:不同特质走不同实现路径、折叠泛型打印、字段有无选择(SFINAE 的替代)。
和运行时 if 的区别:if constexpr 是编译期丢弃分支,不会为另一分支生成代码;条件必须是常量表达式。
五、编译期数据结构运算(简要提)
借助 constexpr + 递归/折叠可做编译期数组/字符串/集合运算:
constexpr auto arr = make_array(...);把表编译进常量。- 折叠展开的索引生成(integer_sequence → 调包展开)。
- 编译期 config:多用
if constexpr+consteval生成查表,避免启动期初始化。
常用配套工具:
std::integer_sequence/std::make_index_sequence:展开个数为参数包。- fold expressions (C++17):
(... + args)(args + ...)((args), ...)等聚合操作 pack。
六、给面试官串讲的叙述(组织语言)
「模板元编程分两派:现代常用 constexpr 计算在函数层做编译期算术与逻辑;需要基于类型做分支的用 if constexpr/requires 剪枝(不用老式 enable_if 叠罗汉);而纯粹的类型变换(如从‘容器元素类型’推导、迭代 traits)仍需靠模板递归 + 特化,因为类型没有运行期值。HPC 里我主要用它把 dimension、策略、布局写成编译期参数,让 kernel 可按 template<int> 特化展开与常量折叠。」
七、实战提醒
- 别过度元编程:可读性与编译时间都是成本。面试官更看重你能讲清楚「什么时候该用」。
- 编译器差异/诊断晦涩,遇到难排查可给每个分型加 static_assert 定位。