constexpr 与编译期计算
constexpr是 C++ 将计算从运行时移到编译期的核心机制,实现真正的零运行时开销。
constexpr 变量
cpp
// constexpr 变量:编译期常量
constexpr int MAX_SIZE = 1024;
constexpr double PI = 3.14159265358979;
// 可用于数组大小、模板参数等需要编译期常量的地方
int arr[MAX_SIZE];
std::array<int, MAX_SIZE> arr2;
// const vs constexpr
const int n = 42; // 运行时常量(可以是运行时值)
constexpr int m = 42; // 编译期常量(必须是编译期值)
int runtime_val = get_value();
const int c1 = runtime_val; // OK,运行时 const
// constexpr int c2 = runtime_val; // 错误!不是编译期值constexpr 函数
cpp
// C++11:constexpr 函数(限制较多)
constexpr int square(int x) { return x * x; }
// C++14:放开限制,支持局部变量、循环、条件
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) result *= i;
return result;
}
static_assert(factorial(10) == 3628800); // 编译期验证
// C++17:constexpr if
template<typename T>
constexpr auto abs_val(T x) {
if constexpr (std::is_unsigned_v<T>) {
return x; // 无符号类型不需要取绝对值
} else {
return x < 0 ? -x : x;
}
}
// C++20:几乎所有代码都可以是 constexpr
constexpr std::vector<int> make_vec() { // C++20 允许!
std::vector<int> v;
for (int i = 0; i < 5; ++i) v.push_back(i * i);
return v;
}consteval 与 constinit(C++20)
cpp
// consteval:强制编译期求值(比 constexpr 更严格)
consteval int compile_only(int x) { return x * x; }
constexpr int a = compile_only(5); // OK,编译期
// int b = compile_only(runtime_val); // 错误!必须编译期
// constinit:保证静态变量在编译期初始化(避免静态初始化顺序问题)
constinit int global = factorial(5); // 保证编译期初始化
// constinit int bad = get_runtime_value(); // 错误!
// 对比:
// constexpr = 编译期常量(不可修改)
// constinit = 编译期初始化(可以修改)
// consteval = 只能在编译期调用编译期数据结构
cpp
#include <array>
#include <algorithm>
// 编译期查找表
constexpr std::array<int, 10> make_squares() {
std::array<int, 10> arr{};
for (int i = 0; i < 10; ++i) arr[i] = i * i;
return arr;
}
constexpr auto SQUARES = make_squares();
static_assert(SQUARES[5] == 25);
// 编译期字符串处理
constexpr bool starts_with(std::string_view s, std::string_view prefix) {
return s.size() >= prefix.size() &&
s.substr(0, prefix.size()) == prefix;
}
static_assert(starts_with("hello world", "hello"));
// 编译期哈希(用于 switch 字符串)
constexpr uint64_t hash_str(std::string_view s) {
uint64_t h = 14695981039346656037ULL;
for (char c : s) {
h ^= static_cast<uint64_t>(c);
h *= 1099511628211ULL;
}
return h;
}
void dispatch(std::string_view cmd) {
switch (hash_str(cmd)) {
case hash_str("start"): /* ... */ break;
case hash_str("stop"): /* ... */ break;
default: break;
}
}if constexpr
cpp
#include <type_traits>
// 编译期条件分支,未选中的分支不参与编译
template<typename T>
std::string to_string(T val) {
if constexpr (std::is_same_v<T, bool>) {
return val ? "true" : "false";
} else if constexpr (std::is_integral_v<T>) {
return std::to_string(val);
} else if constexpr (std::is_floating_point_v<T>) {
return std::to_string(val);
} else {
// 对于其他类型,要求有 to_string 成员
return val.to_string();
}
}
// 递归终止
template<typename T>
void print_all(T&& t) {
std::cout << t << "\n";
}
template<typename T, typename... Rest>
void print_all(T&& first, Rest&&... rest) {
std::cout << first << " ";
if constexpr (sizeof...(rest) > 0) {
print_all(std::forward<Rest>(rest)...);
}
}实际应用:编译期配置
cpp
// 编译期特性检测与配置
#ifdef NDEBUG
constexpr bool DEBUG_MODE = false;
constexpr int LOG_LEVEL = 0;
#else
constexpr bool DEBUG_MODE = true;
constexpr int LOG_LEVEL = 3;
#endif
template<int Level>
void log(const char* msg) {
if constexpr (Level <= LOG_LEVEL) {
std::cout << "[" << Level << "] " << msg << "\n";
}
// 高于 LOG_LEVEL 的日志在编译期被完全消除
}
// 编译期单位换算
constexpr long long operator""_KB(unsigned long long n) { return n * 1024; }
constexpr long long operator""_MB(unsigned long long n) { return n * 1024 * 1024; }
constexpr long long operator""_GB(unsigned long long n) { return n * 1024 * 1024 * 1024; }
constexpr auto BUFFER_SIZE = 4_MB;
constexpr auto MAX_FILE_SIZE = 2_GB;
static_assert(BUFFER_SIZE == 4194304);关键认知
constexpr 的核心价值是将运行时开销转移到编译期。C++20 大幅扩展了 constexpr 的能力,几乎所有标准库算法都可以在编译期使用。if constexpr 是替代 SFINAE 的更清晰方式。