Skip to content

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 的更清晰方式。

系统学习 C++ 生态,深入底层架构