常量折叠是编译期对constexpr表达式或字面量组合直接求值的过程,如int x = 2 + 3 * 4被替换为14,不生成运行时运算指令,要求所有操作数为编译期已知常量且运算符支持常量求值。

常量折叠在编译期就完成计算,不是运行时行为
常量折叠是编译器对 constexpr 表达式或字面量组合直接求值的过程,比如 int x = 2 + 3 * 4; 在目标代码里根本不会出现乘法和加法指令,而是直接替换成 14。它不依赖运行时环境,也不触发任何函数调用或对象构造——只要所有操作数都是编译期已知的常量,且运算符支持常量求值(如算术、位运算、逻辑非等),编译器就会做。
常见错误现象:你以为写了 const int a = rand() % 10; 就能被折叠?不行。rand() 不是常量表达式,这行连编译都过不了(C++11 起)。即使写成 const int a = 5;,若没参与后续计算,也可能被优化掉,不等于“发生了折叠”。
- 只作用于纯右值常量表达式,
const变量本身不够,必须是constexpr或字面量组合 - 数组大小、模板非类型参数、
static_assert条件都依赖它;没它,这些地方根本没法写确定值 - 浮点字面量折叠可能受
-ffloat-store或/fp:strict影响,结果未必和运行时一致
哪些表达式能被常量折叠?看 constexpr 规则就行
C++ 标准规定:能放进 constexpr 函数体或变量初始化中的表达式,基本都能被折叠。但要注意边界——比如 sizeof(int)、alignof(double)、字面量字符串长度 sizeof("abc") - 1 都行;而 std::sqrt(4.0) 不行(除非你用的是 C++23 的 std::sqrt 特化版本,且编译器支持)。
使用场景很实际:写模板元编程时,std::array<int n></int> 的 N 必须是常量折叠结果;switch 的 case 值也必须能折叠,否则编译报错 case label does not reduce to an integer constant。
立即学习“C++免费学习笔记(深入)”;
1 ✅ 折叠为 <code>1048576;1 (<code>n是变量) ❌ 不折叠-
"hello"[0]✅ 折叠为'h';s[0](s是std::string) ❌ 不行,std::string不是字面量类型 -
__builtin_popcount(0b1010)在 GCC/Clang 中通常可折叠,但属于扩展,跨编译器不保
开启 -O2 后它自动发生,但别指望它“修复”你的 const 写法
常量折叠是优化的副产品,不是独立开关。你不需要手动启用,只要用了支持的语法(constexpr、字面量、整型常量表达式),且开了优化(-O1 及以上),它就在那儿。但很多人误以为写个 const int x = 10; 就万事大吉——其实 const ≠ 编译期常量,尤其当它绑定到非常量左值时:
int y = 5; const int x = y; // x 不是常量表达式,不能用于数组维度或 template 参数
性能影响几乎为零:它发生在编译前端,不增加构建时间,反而减少生成指令数。兼容性方面,C++11 起完全标准化,但旧代码里大量用 #define 模拟折叠,现在应优先用 constexpr。
-
constexpr int f() { return 42; }定义后,f()在任何上下文都可折叠 -
constinit(C++20)可确保变量在编译期初始化,但它不保证表达式本身可折叠,只是防止动态初始化 - 调试时看不到折叠过程:反汇编里找不到原始表达式,只有结果值,容易让人困惑“我写的计算去哪了”
容易被忽略的坑:字符串字面量拼接和用户定义字面量
字符串字面量拼接("a" "b")是翻译阶段 6 的行为,早于常量折叠,所以它不算“折叠”,但效果类似。真正容易翻车的是用户定义字面量(UDL):只有带 constexpr 修饰的 UDL 才能参与折叠。
比如 123_km 若定义为 constexpr distance operator"" _km(long double v),那 123_km + 45_km 就可能折叠;但如果返回的是 std::string 或调用了 new,就彻底出局。
-
"abc"s + "def"s(std::string字面量)❌ 不可折叠,因为operator+不是constexpr -
std::string_view{"abc"}✅ 可折叠,构造函数是constexpr - 模板中用
sizeof...(Args)是折叠,但sizeof(T)不是——它是编译期已知的,但不属于“折叠”,而是类型系统固有属性
最麻烦的是跨翻译单元:一个 constexpr 函数在 A.cpp 定义,在 B.cpp 调用,若没内联或未开启 LTO,某些编译器可能放弃折叠。这时候得靠 inline constexpr 或把定义放进头文件。









