如何在c++中实现编译期计算? (constexpr函数)

constexpr函数需满足:函数体仅含声明、return、字面量运算及constexpr函数调用;禁用try、goto、static变量、非字面量类型;参数与返回值须为字面量类型;不可调用非constexpr函数;递归深度受编译器限制。

constexpr 函数必须满足哪些约束?

不是所有函数加个 constexpr 就能进编译期。它本质是“编译器可静态求值的纯函数”,核心限制有:
函数体只能包含声明、return、字面量运算、其他 constexpr 函数调用;不能有 trygotostatic 变量、非字面量类型成员;C++14 起允许有限循环和局部变量,但所有操作仍需在编译期可判定。

  • 返回类型和所有参数类型必须是字面量类型(intstd::array、用户定义的带 constexpr 构造函数的类等)
  • 函数体内不能调用非 constexpr 函数(比如 std::sqrt 在 C++20 前不是 constexpr
  • 递归深度受编译器限制(如 GCC 默认 512 层),过深会报错 constexpr evaluation exceeded maximum depth

如何写一个真正能在编译期展开的阶乘函数?

下面是一个 C++14 兼容的 constexpr 阶乘实现,它在模板实例化或变量初始化时触发编译期计算:

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

// 编译期使用示例 constexpr int fact_5 = factorial(5); // OK:5! = 120,存为字面量 static_assert(fact_5 == 120, "compile-time check");

// 错误用法(运行时输入无法用于 constexpr 上下文) // int x = 5; // constexpr int bad = factorial(x); // ❌ error: 'x' is not a constant expression

注意:factorial(5) 在编译时就被替换成 120,生成的汇编里不会出现任何乘法指令;而若传入变量或未标记 constexpr 的表达式,编译器会退化为运行时调用(C++14+)或直接报错(C++11)。

为什么 std::array 的 size() 不是 constexpr?

这是常见误解点:std::array::size() 在 C++17 前**不是** constexpr,导致类似 std::array{}.size() 无法用于模板非类型参数。原因在于早期标准未将该成员函数标注为 constexpr —— 它只是返回 N,但语言规则没允许。

  • C++17 起 std::array::size() 已是 constexpr,可安全用于模板参数,如:template void f(std::array); f(arr);
  • 若需兼容旧标准,应直接用 std::array::size() 的模板参数 N,而非调用成员函数
  • 自定义容器务必显式加 constexpr 标签,否则即使逻辑简单也不会被编译器信任

constexpr 和 consteval 的关键区别在哪?

consteval(C++20 引入)是更严格的“仅编译期”保证:它强制函数**必须**在编译期求值,任何运行时调用都会编译失败。而 constexpr 是“可编译期,也可运行时”——取决于调用上下文。

consteval int square(int x) {
    return x * x;
}

constexpr int cube(int x) { return x x x; }

int main() { constexpr int s1 = square(4); // ✅ OK // int s2 = square(4); // ❌ error: call to consteval function 'square' is n

ot a constant expression

constexpr int c1 = cube(4);     // ✅ 编译期
int c2 = cube(4);               // ✅ 运行时(允许)

}

实际项目中,优先用 constexpr 保持灵活性;只有当你明确禁止运行时路径(例如实现元编程断言、生成唯一编译期哈希),才用 consteval。别为了“看起来更编译期”而滥用 consteval,它会显著降低函数复用性。