一、核心作用
constexpr 是 C++11 引入的关键字,用于修饰函数(也可修饰变量),表示该函数支持编译期求值:当传入编译期常量作为参数时,可在编译阶段计算出结果,其返回值可作为常量表达式使用(如数组长度、模板非类型参数、枚举初始化等);当传入运行期变量时,也可作为普通函数在运行时执行。
二、C++11 对 constexpr 函数的严格语法要求
C++11 是 constexpr 的首个版本,对函数的约束非常严苛,核心要求如下:
1. 类型约束
- 返回类型必须是字面类型(LiteralType) ,且不能是
void。 - 所有形参的类型也必须是字面类型。
字面类型包括:算术类型(int/double 等)、枚举类型、指针 / 引用类型,以及满足条件的类类型(拥有 constexpr 构造函数、无虚函数、析构函数平凡)。
2. 函数体的强限制
C++11 要求 constexpr 函数体逻辑极度简单,函数体内只能包含以下内容:
- 空语句、
static_assert静态断言 typedef/using类型别名声明- 恰好一条
return语句
绝对禁止 :if/switch 分支语句、for/while 循环语句、局部变量定义、赋值操作、goto 等控制流或副作用语句。
注意:
return后的表达式中可以使用三元运算符?:、逻辑运算符、递归调用自身 ------ 这些属于表达式而非语句,是合法的。
3. 其他约束
- 函数不能是虚函数。
- 非静态成员函数被
constexpr修饰时,隐含为const成员函数,不能修改类的成员变量。 constexpr函数默认是inline的,通常定义在头文件中,以保证编译期可见。- 函数不能包含副作用(如修改全局变量、执行 I/O 操作等),编译期求值无法产生运行期副作用。
三、典型使用示例
1. 基础算术函数
cpp
运行
cpp
// C++11 合法:仅一条 return 语句
constexpr int square(int x) {
return x * x;
}
// 编译期使用:作为数组长度
int arr[square(5)]; // 等价于 int arr[25];
// 作为模板非类型参数
template<int N> struct Test {};
Test<square(4)> t; // 实例化为 Test<16>
2. 递归实现(允许递归调用)
C++11 支持 constexpr 函数递归,可通过递归 + 三元运算符实现循环逻辑:
cpp
运行
cpp
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int res = factorial(5); // 编译期计算得到 120
3. 类的 constexpr 成员函数
cpp
运行
cpp
class Point {
int x, y;
public:
// constexpr 构造函数
constexpr Point(int x_, int y_) : x(x_), y(y_) {}
// constexpr 成员函数,隐含 const 属性
constexpr int getX() const { return x; }
constexpr int getY() const { return y; }
};
constexpr Point p(10, 20);
constexpr int px = p.getX(); // 编译期获取值 10
四、关键特性与注意事项
-
编译期 / 运行期双模式
- 实参为编译期常量时,函数在编译期求值,结果为常量表达式;
- 实参为运行期变量时,函数退化为普通函数,在运行时执行。
cpp
运行
int a = 10; int b = square(a); // 运行时计算,完全合法 -
不强制编译期求值
constexpr仅表示 "具备编译期求值能力",而非 "必须在编译期执行"。只有当结果被用于常量表达式语境(数组长度、模板参数等)时,编译器才会强制编译期计算。 -
C++11 的局限性 C++11 的
constexpr函数能力非常受限,无法实现复杂的多分支、循环逻辑。C++14 大幅放松了限制,允许局部变量、分支 / 循环语句、void返回类型等。
五、C++11 中不合法的示例
cpp
运行
cpp
// 错误:存在 if 语句(控制流语句不允许)
constexpr int abs(int x) {
if (x < 0) return -x;
return x;
}
// 错误:存在局部变量和 for 循环
constexpr int sum(int n) {
int s = 0;
for (int i = 1; i <= n; ++i) s += i;
return s;
}
// 错误:返回 void 类型
constexpr void func() {}
从 C++11 首次引入 constexpr 开始,后续每一个标准版本都在持续放宽限制、扩展能力:从最初 "只能写一条 return" 的极简编译期函数,逐步演进到 C++20/23 接近通用的编译期计算体系。下面按版本顺序梳理核心更新与使用方式。
一、C++14:大幅放松函数体限制
C++14 是 constexpr 第一个重要扩展版本,核心是放开函数体的语句限制,让编译期函数可以编写常规逻辑代码。
核心放宽规则
-
函数体允许多条语句与常规控制流 不再强制只能有一条
return,支持:- 局部变量定义(必须是字面类型,且必须带初始化,不允许未初始化变量)
if/switch分支语句for/while/do-while循环语句- 赋值、自增 / 自减等修改操作
- 仍然禁止:
goto、try-catch、动态内存分配、I/O 操作、静态变量等。
-
允许
void返回类型constexpr函数可以返回void,用于编写编译期执行的过程式逻辑(如交换、赋值操作)。 -
取消成员函数的隐含
constC++11 中constexpr成员函数默认是const的,无法修改成员变量;C++14 起取消该限制,非const的constexpr成员函数可以修改对象状态(只要对象在编译期可修改)。
代码示例
cpp
运行
cpp
// 1. 分支语句:C++11 非法,C++14 合法
constexpr int abs(int x) {
if (x < 0)
return -x;
return x;
}
// 2. 循环 + 局部变量:C++11 非法,C++14 合法
constexpr int sum(int n) {
int s = 0;
for (int i = 1; i <= n; ++i) {
s += i;
}
return s;
}
// 3. void 返回的 constexpr 函数
constexpr void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
// 4. 可修改成员的 constexpr 成员函数
class Counter {
int val;
public:
constexpr Counter(int v) : val(v) {}
constexpr void inc() { val++; } // 修改成员
constexpr int get() const { return val; }
};
constexpr Counter build_counter() {
Counter c(0);
c.inc();
c.inc();
return c;
}
constexpr auto c = build_counter(); // 编译期构造+修改,c.get() == 2
二、C++17:扩展适用场景 + constexpr lambda
C++17 重点扩展 constexpr 的应用范围,解决工程化问题,并引入 lambda 支持。
核心更新
-
constexpr lambda 表达式 lambda 可以显式声明为
constexpr;如果 lambda 的函数体天然满足constexpr函数要求,即使不写关键字,其operator()也会默认是constexpr的。cpp
运行
cpp// 显式 constexpr lambda constexpr auto square = [](int x) { return x * x; }; constexpr int res1 = square(5); // 编译期计算 = 25 // 隐式 constexpr lambda(满足条件自动支持) auto add = [](int a, int b) { return a + b; }; constexpr int res2 = add(3, 4); // 编译期计算 = 7 -
constexpr 变量默认 inline 命名空间作用域的
constexpr变量默认具备inline属性,可以直接定义在头文件中,被多个翻译单元包含也不会出现「多重定义」链接错误,彻底解决了 C++11/14 中头文件 constexpr 变量的链接问题。 -
if constexpr(编译期分支) 配合模板元编程使用,可以在编译期根据条件丢弃不选中的代码分支,常与
constexpr函数结合实现编译期逻辑分发。cpp
运行
cpp#include <type_traits> template<typename T> constexpr T double_value(T t) { if constexpr (std::is_integral_v<T>) { return t * 2; } else { return t; } } -
其他补充
- 放宽字面类型限制,更多自定义类可以作为字面类型用于编译期;
- 标准库大规模 constexpr 化:
std::array、std::string_view、多数算法等开始支持编译期调用。
三、C++20:里程碑式升级 ------ 接近通用编译期计算
C++20 是 constexpr 能力的质变版本,打破了绝大多数核心限制,编译期计算能力接近运行时,同时新增两个互补关键字完善语义。
1. 语言层面的核心放宽
-
允许编译期动态内存分配 编译期可以使用
new/delete分配与释放内存,要求分配的内存必须在编译期全部释放,不能逃逸到运行时 (即不能将编译期分配的指针返回给运行期使用)。基于此,std::string、std::vector等标准容器开始支持constexpr。 -
允许 constexpr 虚函数与编译期多态 虚函数可以声明为
constexpr,支持编译期的虚函数调用与多态行为。 -
允许 constexpr 析构函数 类的析构函数可以声明为
constexpr,字面类型不再强制要求析构函数是平凡的。 -
允许 try-catch 块
constexpr函数中可以编写try-catch结构,但仍禁止throw表达式,不能主动抛出异常,仅支持结构化的错误捕获框架。 -
仍然禁止的内容:
goto语句、静态存储期变量、线程局部变量、I/O 操作、内联汇编等。
2. 新增三个配套关键字
C++20 引入 consteval、constinit,和 constexpr 形成互补,明确不同的编译期语义:
表格
| 关键字 | 语义 | 典型使用场景 |
|---|---|---|
constexpr |
函数 / 变量:既可编译期也可运行期 | 通用编译期计算,双模式兼容 |
consteval |
函数:强制编译期求值,运行时不可调用 | 必须在编译期完成计算的场景 |
constinit |
变量:强制静态初始化,变量本身可修改 | 解决全局变量初始化顺序问题 |
示例:
cpp
运行
cpp
// consteval:只能编译期调用
consteval int factorial(int n) {
int res = 1;
for (int i = 2; i <= n; ++i) res *= i;
return res;
}
constexpr int v1 = factorial(5); // 正确:编译期计算
int x = 5;
int v2 = factorial(x); // 错误:x 不是编译期常量
// constinit:保证静态初始化,变量可修改
constinit int global_counter = 0;
3. 编译期容器示例(C++20)
cpp
运行
cpp
#include <vector>
#include <numeric>
constexpr int sum_1_to_n(int n) {
std::vector<int> nums;
for (int i = 1; i <= n; ++i) {
nums.push_back(i);
}
return std::accumulate(nums.begin(), nums.end(), 0);
}
constexpr int result = sum_1_to_n(10); // 编译期计算得到 55
四、C++23:细节补全与易用性提升
C++23 在 C++20 的基础上补全能力边界,让编译期编程更贴近常规代码写法。
核心更新
-
允许 throw 表达式与完整异常处理 在
consteval函数中允许使用throw抛出异常,且可以被catch捕获;若异常未在编译期捕获,则直接触发编译错误。编译期错误处理能力与运行时对齐。 -
if consteval:区分编译期 / 运行期路径 在
constexpr函数内部判断当前是否处于编译期求值上下文,从而为编译期和运行期分别提供不同实现。cpp
运行
cpp#include <iostream> constexpr int compute(int x) { if consteval { return x * 2; // 编译期分支:纯计算 } else { std::cout << "runtime call\n"; // 运行期分支:可包含副作用 return x * 2; } } constexpr int a = compute(10); // 走编译期分支 int b = compute(20); // 走运行期分支,打印输出 -
其他扩展
- 支持
constevallambda 表达式; - 允许 constexpr 联合体、位域操作;
- 标准库进一步 constexpr 化,绝大多数算法和容器都支持编译期使用。
- 支持
五、版本演进总览
表格
| 核心特性 | C++11 | C++14 | C++17 | C++20 | C++23 |
|---|---|---|---|---|---|
| 单 return 语句 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 分支 / 循环 / 局部变量 | ❌ | ✅ | ✅ | ✅ | ✅ |
| void 返回值 | ❌ | ✅ | ✅ | ✅ | ✅ |
| constexpr lambda | ❌ | ❌ | ✅ | ✅ | ✅ |
| 动态内存分配 | ❌ | ❌ | ❌ | ✅ | ✅ |
| 虚函数 / 析构函数 | ❌ | ❌ | ❌ | ✅ | ✅ |
| consteval / constinit | ❌ | ❌ | ❌ | ✅ | ✅ |
| throw 异常 | ❌ | ❌ | ❌ | ❌ | ✅ |
| if consteval | ❌ | ❌ | ❌ | ❌ | ✅ |
cpp
constexpr int sum(int n) {
int s = 0;
for (int i = 1; i <= n; ++i) s += i;
return s;
}
// 错误:返回 void 类型
constexpr void func() {}