| 标准版本 |
特点 |
| C++11 |
引入 constexpr 变量和函数;函数体限单条 return 语句 |
| C++14 |
放宽 constexpr 函数限制:允许局部变量、多个 return、循环等 |
| C++17 |
引入 if constexpr;constexpr static 成员变量可内联初始化;允许 constexpr Lambda |
| C++20 |
允许 constexpr 虚函数、try-catch、dynamic_cast、typeid;引入 consteval 和 constinit |
一、什么是 constexpr
C++11 中新增加了用于指示常量表达式的 constexpr 关键字。
constexpr 表示可以在编译器求值(但并不强制)。它允许把表达式/函数/构造放在编译器计算,从而用于需要常量表达式的场景(如数组大小、模板参数、static_assert 等)。
constexpr 修饰函数/构造函数时,表示该函数可以在编译期被调用;在运行时也可以被正常调用。若你需要强制在编译期求值,用 consteval (C++20)。
C++17 引入了 if constexpr(编译期分支),C++20 又加入了 consteval 与 constinit 等扩展,整体上 constexpr 能做的事情随着 C++ 标准演进越来越多。
基本语法与要求:
1 2 3 4 5 6 7 8 9
| constexpr int maxSize = 100; constexpr double pi = 3.14159265359;
int n = 10;
const int m = 20; constexpr int size = m;
|
二、constexpr 与 const
不过要注意区分 const 和 constexpr。
修饰对象的时候两者之间最基本的区别是:
const 修饰一个对象表示它是常量。这表示对象一经初始化就不会再变动了,并且允许编译器使用这个特点优化程序。这也防止程序员修改了本不应该修改的对象。
constexpr 是修饰一个常量表达式。但请注意 constexpr 不是修饰常量表达式的唯一途径。
1 2 3 4 5 6 7 8
| constexpr int x = 10;
constexpr int add(const int a, const int b) { return a + b; }
|
修饰函数的时候两者之间最基本的区别是:
const 只能用于非静态成员的函数而不是所有函数。它保证成员函数不修改任何非静态数据。
constexpr 可以用于含参和无参函数。constexpr 函数适用于常量表达式,只有在下面的情况下编译器才会接受constexpr 函数:
- 函数体必须足够简单,除了
typedef 和静态元素,只允许有 return 语句。如构造函数只能有初始化列表,typedef 和静态元素 (实际上在 C++14 标准中已经允许定义语句存在于 constexpr 函数体内了)
- 参数和返回值必须是字面值类型
1 2 3
| constexpr int square(int x) { return x * x; };
constexpr int val = square(4);
|
| 特性 |
const |
constexpr |
| 求值时机 |
运行期(或编译期,不保证) |
保证编译期 |
| 编译期常量 |
不一定是 |
一定是 |
| 用途 |
只读语义 |
编译期计算、数组大小、模板参数等 |
| 变量修饰 |
可修饰运行时变量 |
只能修饰编译期可确定的对象 |
| 函数修饰 |
N/A |
函数可在编译期执行 |
三、constexpr 的使用
3.1 基本用法
把 constexpr 放在函数或构造函数前,表示函数可以在常量表达式上下文被求值。调用该函数时如果参数都是常量表达式,编译器会在编译期执行它并产生结果。
重要一点:constexpr 并不强制编译期求值——如果传入的是运行时值,函数会在运行时执行 (行为像普通函数)。如果你需要强制编译期求值,使用 consteval (见下)。
1 2 3 4 5 6 7
| constexpr int factorial(int n) { return n <= 1 ? : (n * factorial(n - 1)); }
static_assert(factorial(5) == 120); int x = 6; int r = factorial(x);
|
3.2 使用注意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| int runtime_n;
int runtimeFunc() { return 42; } constexpr int badFunc() { return 42; }
int global = 10; constexpr int* p1 = &global; void f() { int local = 20; }
constexpr std::vector<int> v{1,2,3};
void func(const int& x); void func(int& x);
constexpr int val = 10; func(val);
constexpr int bad_recursion(int n) { return bad_recursion(n); }
struct Base { virtual constexpr ~Base() = default; }; struct Derived : Base {};
constexpr Derived d; constexpr Base b = d;
|
四、各版本演进
constexpr 函数的演进是 C++ 编译期计算能力扩张的核心。
4.1 C++11
C++11 的 constexpr 限制比较多,几乎只能是用于纯函数式的:
1 2 3 4 5 6 7 8
| constexpr int factorial_cpp11(int n) { return (n <= 1) ? 1 : (n * factorial_cpp11(n - 1)); }
constexpr int fact5 = factorial_cpp11(5);
|
限制:
- 不能声明变量
- 不能使用
if、for、while(只能用 ?: 和递归)
- 不能有
void 返回(C++11 标准原文如此,实际有例外)
4.2 C++14
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| constexpr int factorial_cpp14(int n) { int result = 1; for (int i = 2; i <= n; ++i) { result *= i; } return result; }
constexpr int gcd(int a, int b) { while (b != 0) { int temp = b; b = a % b; a = temp; } return a; }
|
4.3 C++17
1 2 3 4 5 6 7 8 9 10 11
| constexpr int complexLogic(int n) { if (n < 0) return 0; switch (n) { case 0: return 1; case 1: return 2; default: return n * 2; } }
|
C++17 起,Lambda 在适当条件下隐式成为 constexpr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| constexpr auto square = [](int n) { return n * n; }; constexpr int nine = square(3);
constexpr auto add = [](int a, int b) constexpr { return a + b; };
auto generic = [](auto a, auto b) { if constexpr (std::is_same_v<decltype(a), decltype(b)>) { return a + b; } };
|
4.4 C++20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| struct Base { virtual constexpr int getValue() const { return 1; } virtual constexpr ~Base() = default; };
struct Derived : Base { constexpr int getValue() const override { return 2; } };
constexpr int callVirtual(const Base& b) { return b.getValue(); }
constexpr Derived d; constexpr int val = callVirtual(d);
constexpr int safeDivide(int a, int b) { if (b == 0) throw std::runtime_error("Division by zero"); return a / b; }
constexpr int* makeArray() { int* p = new int[5]{1,2,3,4,5}; int sum = p[0] + p[4]; delete[] p; return sum; }
|
4.4.1 consteval——立即函数(强制编译期)
consteval 表示立即函数:函数被标记为 consteval 式,所有可能被求值的调用必须在编译期产生常量表达式,否则就是编译错误。也就是说,调用时强制在编译期执行(不能在运行时执行)。
这是 constexpr 的补充:constexpr 时可以在编译期求值,而 consteval 时必须在编译期求值。
1 2 3
| consteval int make_magic() { return 42; } const expr int id = make_magic(); int x = make_magic();
|
4.4.2 constinit——与静态初始化顺序相关
constinit 并不会使变量成为常量 (它并不隐含 const),它的目的是保证具有静态/线程存储期的变量进行静态初始化而不是动态初始化;如果其初始化不是常量初始化,程序就是 informed (违例)。因此 constinit 常用于防止静态初始化顺序混乱(static initialization order fiasco)。简而言之:constinit 是关于“初始化时机”的断言,而非关于是否常量。
1 2
| constexpr int compile_time_val = 10; constinit int runtime_global = compile_time_val;
|
注意:constexpr 隐含 constinit 的初始化要求;但 constinit 本身不隐含 const。
五、constexpr if (C++17)
if constexpr(cond) 是编译期分支,不是运行时 if,用于替代冗长的 SFINAE 和模板特化。
5.1 基本语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template <typename T> auto getValue(T t) { if constexpr (std::is_pointer_v<T>) { return *t; } else { return t; } }
int i = 10; int* p = &i;
auto v1 = getValue(i); auto v2 = getValue(p);
|
5.2 SFINAE vs constexpr if
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template <typename T> typename std::enable_if_t<std::is_pointer_v<T>, std::remove_pointer_t<T>> getValue_sfinae(T t) { return *t; }
template <typename T> typename std::enable_if_t<!std::is_pointer_v<T>, T> getValue_sfinae(T t) { return t; }
template <typename T> auto getValue_clean(T t) { if constexpr (std::is_pointer_v<T>) { return *t; } else { return t; } }
|
5.3 类模板特化的替代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| template <typename T, typename Enable = void> struct Container { void print() const {}; };
template <typename T> struct Container<T, std::enable_if_t<std::is_integral_v<T>>> { void print() const { std::cout << "integral\n"; } };
template <typename T> struct ModernContainer { void print() const { if constexpr (std::is_integral_v<T>) { std::cout << "integral: " << value << "\n"; } else { std::cout << "non-integral\n"; } } T value; };
|
六、constexpr 的优化意义
编译期计算的直接效果:
1 2 3 4 5 6 7 8 9 10 11 12
| constexpr int fib(int n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); }
constexpr int fib20 = fib(20);
int runtime_call(int n) { return fib(n); }
|
零开销抽象:
1 2 3 4 5 6 7 8 9 10 11 12 13
| constexpr uint32_t hash(const char* str, uint32_t h = 0) { return str[0] == 0 ? h : hash(str + 1, h * 31 + str[0]); }
switch (hash("hello")) { case hash("hello"): break; case hash("world"): break; case hash("foobar"): break; }
|
内存布局优势:
1 2 3 4 5 6 7 8 9
| constexpr std::array<int, 1000> lookupTable = generateTable();
std::array<int, 1000> generateRuntime() { std::array<int, 1000> result; return result; }
|
七、constexpr 与模板元编程的结合
这是 constexpr 最强大的应用场景,将模板元编程从模板特化的繁琐语法中解放出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
template <class T, T v> struct m_integral_constant { static constexpr T value = v; using value_type = T; using type = m_integral_constant; constexpr operator value_type() const noexcept { return value; } constexpr value_type operator()() const noexcept { return value; } };
template <bool B> using m_bool_constant = m_integral_constant<bool, B>;
using m_true_type = m_bool_constant<true>; using m_false_type = m_bool_constant<false>;
namespace classic { template <class T> struct is_pair : m_false_type {};
template <class T1, class T2> struct is_pair<std::pair<T1, T2>> : m_true_type {}; }
namespace modern { template <class T> struct is_pair : m_false_type {};
template <class T1, class T2> struct is_pair<std::pair<T1, T2>> : m_true_type {}; template <class T> inline constexpr bool is_pair_v = is_pair<T>::value; }
template <class T> concept Pair = requires(T t) { typename T::first_type; typename T::second_type; t.first; t.second; };
|