Skip to content

编译期计算

constexpr 的演进

constexpr 从 C++11 的受限函数开始,每个标准版本都显著扩展了可在编译期执行的代码范围。

C++11:单 return 语句

cpp
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);

C++14:完整函数体

允许局部变量、循环、if/switch:

cpp
constexpr int gcd(int a, int b) {
    while (b != 0) { int t = b; b = a % b; a = t; }
    return a;
}
constexpr auto sq = [](int x) { return x * x; };

C++17:if constexpr 与编译期容器操作

cpp
template <typename T>
constexpr auto absolute(T val) {
    if constexpr (std::is_unsigned_v<T>) return val;
    else return val < 0 ? -val : val;
}

constexpr auto make_lookup_table() {
    std::array<int, 10> table{};
    for (int i = 0; i < 10; ++i) table[i] = i * i;
    return table;
}
constexpr auto sq_table = make_lookup_table();

C++20:consteval、constinit 与重大扩展

允许 dynamic allocation(编译期内不得泄漏)、virtual 函数、union、try-catch:

cpp
constexpr auto make_vector() {
    std::vector<int> v{1, 2, 3};
    v.push_back(4);
    return v;
}

struct Base { constexpr virtual int value() const = 0; };
struct Derived : Base { constexpr int value() const override { return 42; } };
constexpr int v = Derived{}.value();

C++23:constexpr string/vector

C++20/23 允许在常量求值期间使用 std::stringstd::vector——它们的构造、修改和析构操作都可以在编译期执行。这使得编译期计算从"受限的标量操作"走向"可以使用动态容器"。但需注意:编译期内存分配有上限,且动态分配的存储通常不能跨越该次常量求值存活。

cpp
constexpr auto sorted = [] {
    std::array data{5, 3, 1, 4, 2};
    std::ranges::sort(data);
    return data;
}();

consteval 与 constinit(C++20)

cpp
// consteval — 必须在编译期求值
consteval unsigned fourcc(char a, char b, char c, char d) {
    return unsigned(a) | (unsigned(b) << 8) | (unsigned(c) << 16) | (unsigned(d) << 24);
}
constexpr auto jpeg = fourcc('J','F','I','F');

// constinit — 保证编译期初始化,避免静态初始化顺序惨案
constinit auto global_config = Config{};

编译期反射(C++26)

cpp
consteval std::size_t enum_count(auto enum_type) {
    return std::meta::enumerators_of(enum_type).size();
}
enum class Color { Red, Green, Blue };
static_assert(enum_count(^^Color) == 3);

// 编译期生成 enum→string
constexpr std::string_view color_name(Color c) {
    template for (constexpr auto e : std::meta::enumerators_of(^^Color)) {
        if (c == [:e:]) return std::meta::name_of(e);
    }
    return "unknown";
}

// 编译期遍历结构体成员生成序列化
template <typename T>
std::string to_json(const T& obj) {
    std::string result = "{";
    bool first = true;
    // nonstatic_data_members_of 只遍历数据成员,排除成员函数等
    template for (constexpr auto mem : std::meta::nonstatic_data_members_of(^^T)) {
        if (!first) result += ", "; first = false;
        result += "\"" + std::string(std::meta::name_of(mem)) + "\"";
        result += ": " + serialize(obj.[:mem:]);
    }
    return result + "}";
}

实用场景

查找表生成

cpp
constexpr auto crc32_table = [] {
    std::array<uint32_t, 256> table{};
    for (uint32_t i = 0; i < 256; ++i) {
        uint32_t crc = i;
        for (int j = 0; j < 8; ++j)
            crc = (crc >> 1) ^ (0xEDB88320u & (-(crc & 1)));
        table[i] = crc;
    }
    return table;
}();

编译期字符串解析

cpp
constexpr uint32_t parse_hex(std::string_view sv) {
    uint32_t result = 0;
    for (char c : sv) {
        result <<= 4;
        if (c >= '0' && c <= '9') result |= c - '0';
        else if (c >= 'a' && c <= 'f') result |= c - 'a' + 10;
        else if (c >= 'A' && c <= 'F') result |= c - 'A' + 10;
    }
    return result;
}
constexpr auto color = parse_hex("FF8800");

// 编译期格式字符串验证
consteval bool valid_format(std::string_view fmt) {
    bool percent = false;
    for (char c : fmt) {
        if (c == '%') { percent = !percent; continue; }
        if (percent && c != 'd' && c != 's' && c != '%') return false;
        percent = false;
    }
    return !percent;
}

编译期配置验证

cpp
struct Config {
    static constexpr int max_conn = 100;
    static constexpr int timeout_ms = 5000;
};
consteval bool validate_config() {
    static_assert(Config::max_conn > 0);
    static_assert(Config::timeout_ms < 60000, "timeout too large");
    return true;
}

编译期计算的核心价值:将运行时错误提前到编译期,将运行时计算提前到编译期。前者提升正确性,后者提升性能。"尽可能 constexpr" 已成为现代 C++ 共识。

基于 MIT 许可发布