Day17-constexpr 进阶:编译期计算的艺术

constexpr 进阶:编译期计算的艺术

C++进阶计划 · Day 17 | 预计学习时长:2小时

引言

为什么这个知识点重要

constexpr 从 C++11 引入以来,每个标准都在大幅放松限制。到 C++20/23,constexpr 已经强大到可以在编译期执行几乎任何操作——包括动态内存分配(constexpr new)、容器操作、甚至虚函数调用(要求对象类型在编译期已知,且虚函数是 constexpr 的)。

这意味着你可以在编译期完成以前只能在运行时做的事情:解析 JSON 配置、构建查找表、验证数据格式、甚至执行完整的算法。对于 Qt 应用来说,编译期配置解析可以消除启动时的配置文件解析开销,实现真正的零成本初始化。

与前面内容的关联

  • Day 05 学习了 constexpr 基础和 if constexpr,本节在此基础上大幅深入
  • Day 08 的模板元编程与 constexpr 有天然的协同关系
  • Day 09 的 Concepts 可以在 constexpr 上下文中约束模板参数
  • 本节的编译期配置系统直接关联 Qt 应用的实际工程场景

核心概念

constexpr 能力演进时间线

标准关键突破代表能力
C++11引入 constexpr简单函数、字面量类型
C++14放松限制循环、局部变量、多语句函数
C++17constexpr lambda编译期 lambda 表达式
C++20consteval + constexpr 容器 + constexpr 虚函数 + constexpr new/delete几乎完整的编译期计算
C++23更多标准库 constexpr 化(std::vector、std::string 的 constexpr 支持进一步完善)更广泛的 STL 支持

C++20 的 constexpr 能力边界

// C++20 constexpr 允许的:
// ✓ 动态内存分配(constexpr new/delete)—— 必须在编译期释放
// ✓ 标准容器操作(vector, string, unordered_map 等)
// ✓ 虚函数调用(对象类型在编译期可知时)
// ✗ try-catch 在 constexpr 函数中不可用(C++20 标准中,try-catch 不能在 constexpr 函数体中使用)

//但 C++23 放宽了此限制:throw 表达式可以在 constexpr 函数中使用,但仍不能使用 try-catch 捕获异常。如果抛出异常,编译期求值会失败,转而进行运行时求值(如果函数是 constexpr 而非 consteval)。
// ✓ 大多数 STL 算法

// C++20 constexpr 仍然禁止的:
// ✗ 真正的 I/O 操作
// ✗ 汇编代码
// ✗ 未释放的堆内存(编译期分配必须在编译期释放)
// ✗ 非字面量类型的静态/线程局部变量

代码实战

实战一:constexpr 容器与算法

#include <vector>
#include <string>
#include <algorithm>
#include <array>
#include <iostream>

// C++20 起,std::vector 和 std::string 可以是 constexpr 的
// 这意味着你可以在编译期使用动态数组!

// 编译期排序并构建查找表
constexpr auto build_sorted_lookup() {
    std::vector<int> data = {5, 3, 8, 1, 9, 2, 7, 4, 6, 0};
    std::sort(data.begin(), data.end());
    // ⚠️ 问题:硬编码了 10,如果 data 大小改变会不一致
    // 更好的做法:使用 std::array<int, data.size()> 但 data.size() 在 constexpr 中可用
    constexpr size_t N = data.size();  // C++20 constexpr 中可用
    std::array<int, N> result{};
    for (size_t i = 0; i < N; ++i) {
        result[i] = data[i];
    }
    return result;
}

// 编译器在编译期完成排序!
constexpr auto sorted_table = build_sorted_lookup();

// 编译期字符串处理
constexpr auto build_greeting() {
    std::string result = "Hello";
    result += ", ";
    result += "constexpr";
    result += " ";
    result += "world!";
    return result;
}

constexpr auto greeting = build_greeting();

// 编译期查找表:计算斐波那契数列
constexpr auto build_fib_table() {
    constexpr size_t N = 20;
    std::vector<long long> fibs;
    fibs.push_back(0);
    fibs.push_back(1);
    for (size_t i = 2; i < N; ++i) {
        fibs.push_back(fibs[i-1] + fibs[i-2]);
    }

    std::array<long long, N> result{};
    for (size_t i = 0; i < N; ++i) {
        result[i] = fibs[i];
    }
    return result;
}

constexpr auto fib_table = build_fib_table();

// 编译期二分查找
constexpr int binary_search_lookup(const std::array<int, 10>& table, int target) {
    int lo = 0, hi = static_cast<int>(table.size()) - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (table[mid] == target) return mid;
        if (table[mid] < target) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;  // 未找到
}

// 在编译期计算查找结果
constexpr int found_index = binary_search_lookup(sorted_table, 7);

// 编译期统计
constexpr auto compute_stats() {
    std::vector<double> values = {3.14, 2.71, 1.41, 1.73, 0.577, 1.618};

    double sum = 0;
    for (double v : values) sum += v;
    double mean = sum / static_cast<double>(values.size());

    // 找最大最小
    double mn = values[0], mx = values[0];
    for (double v : values) {
        if (v < mn) mn = v;
        if (v > mx) mx = v;
    }

    return std::array<double, 3>{mean, mn, mx};
}

constexpr auto stats = compute_stats();

int main() {
    // 所有值都在编译期计算完成
    std::cout << "Sorted table: ";
    for (int x : sorted_table) std::cout << x << " ";
    std::cout << "\n";

    std::cout << "Greeting: " << greeting << "\n";

    std::cout << "Fibonacci: ";
    for (long long f : fib_table) std::cout << f << " ";
    std::cout << "\n";

    // static_assert 验证编译期结果
    static_assert(sorted_table[0] == 0);
    static_assert(sorted_table[9] == 9);
    static_assert(found_index == 7);  // 值 7 在排序后数组的索引 7
    static_assert(fib_table[10] == 55);

    std::cout << "Stats: mean=" << stats[0]
              << " min=" << stats[1]
              << " max=" << stats[2] << "\n";

    return 0;
}

实战二:编译期 JSON 解析器

这是一个简化但完整的编译期 JSON 值解析器,展示 constexpr 的真正力量:

#include <string_view>
#include <variant>
#include <optional>
#include <array>
#include <vector>
#include <iostream>
#include <charconv>

// 简化的 JSON 值类型
struct JsonNull {};

struct JsonValue {
    std::variant<
        JsonNull,
        bool,
        long long,
        double,
        std::string_view,  // 使用 string_view 避免编译期堆分配
        std::vector<JsonValue>  // 注意:需要 C++20 的 constexpr vector
    > data;
    // ⚠️ 严重问题:std::variant 在 C++20 中 constexpr 支持不完整
// 尤其是当 variant 包含 std::vector<JsonValue> 时,constexpr 构造可能无法正常工作

// 更可靠的方案:使用自定义 union 或使用 std::variant 但要求 C++23

// 方案1:使用 std::variant(需要 C++23,且编译器支持)
// 在 C++23 中,std::variant 的 constexpr 支持更加完善
// 方案2:手动实现简单 variant(保证 constexpr 兼容)
//template<typename... Ts>
//class Variant {
    // 手动实现 constexpr 兼容的 variant
    // ...
//};

// 注意:原示例代码在 C++20 中可能无法编译!
// 如果要在 C++20 中使用,需要将 variant 替换为更简单的类型
// ⚠️ 递归类型定义问题:std::vector<JsonValue> 在 JsonValue 定义完成前使用
// 虽然 std::vector 可以持有不完全类型(C++17 起),但在 constexpr 上下文中可能有问题

// 更安全的方式:使用 std::vector<std::shared_ptr<JsonValue>> 或 std::unique_ptr<JsonValue>
// 或者使用类型擦除

// 最简单的 constexpr 兼容方案:使用 std::vector<JsonValue> 但使用前向声明
// 注意:std::vector<JsonValue> 在 C++17 中允许持有不完全类型
struct JsonValue;  // 前向声明
// ...
//struct JsonValue {
//    std::variant<...> data;  // 但这里仍然需要 JsonValue 的完整定义
//};

    // 类型检查辅助
    bool is_null() const { return std::holds_alternative<JsonNull>(data); }
    bool is_bool() const { return std::holds_alternative<bool>(data); }
    bool is_int() const { return std::holds_alternative<long long>(data); }
    bool is_double() const { return std::holds_alternative<double>(data); }
    bool is_string() const { return std::holds_alternative<std::string_view>(data); }
    bool is_array() const { return std::holds_alternative<std::vector<JsonValue>>(data); }

    // 取值
    bool as_bool() const { return std::get<bool>(data); }
    long long as_int() const { return std::get<long long>(data); }
    double as_double() const { return std::get<double>(data); }
    std::string_view as_string() const { return std::get<std::string_view>(data); }
};

// 编译期 JSON 解析器
struct JsonParser {
    std::string_view input;
    size_t pos = 0;

    constexpr void skip_whitespace() {
        while (pos < input.size() &&
               (input[pos] == ' ' || input[pos] == '\n' ||
                input[pos] == '\r' || input[pos] == '\t')) {
            ++pos;
        }
    }

    constexpr bool match(std::string_view literal) {
        if (pos + literal.size() > input.size()) return false;
        for (size_t i = 0; i < literal.size(); ++i) {
            if (input[pos + i] != literal[i]) return false;
        }
        pos += literal.size();
        return true;
    }

    constexpr std::optional<JsonValue> parse_value() {
        skip_whitespace();
        if (pos >= input.size()) return std::nullopt;

        char c = input[pos];

        // null
        if (match("null")) return JsonValue{JsonNull{}};

        // true/false
        if (match("true")) return JsonValue{true};
        if (match("false")) return JsonValue{false};

        // 字符串
        if (c == '"') return parse_string();

        // 数字
        if (c == '-' || (c >= '0' && c <= '9')) return parse_number();

        // 数组
        if (c == '[') return parse_array();

        return std::nullopt;
    }

    constexpr std::optional<JsonValue> parse_string() {
        if (input[pos] != '"') return std::nullopt;
        ++pos;
        size_t start = pos;
        while (pos < input.size() && input[pos] != '"') {
            if (input[pos] == '\\') ++pos;  // 跳过转义字符
            ++pos;
        }
        if (pos >= input.size()) return std::nullopt;
        std::string_view str = input.substr(start, pos - start);
        ++pos;  // 跳过结束引号
        return JsonValue{str};
    }

    constexpr std::optional<JsonValue> parse_number() {
        size_t start = pos;
        bool is_float = false;

        if (input[pos] == '-') ++pos;
        while (pos < input.size() && input[pos] >= '0' && input[pos] <= '9') ++pos;

        if (pos < input.size() && input[pos] == '.') {
            is_float = true;
            ++pos;
            while (pos < input.size() && input[pos] >= '0' && input[pos] <= '9') ++pos;
        }

        if (pos < input.size() && (input[pos] == 'e' || input[pos] == 'E')) {
            is_float = true;
            ++pos;
            if (pos < input.size() && (input[pos] == '+' || input[pos] == '-')) ++pos;
            while (pos < input.size() && input[pos] >= '0' && input[pos] <= '9') ++pos;
        }

        std::string_view num_str = input.substr(start, pos - start);

        if (is_float) {
    // ⚠️ 问题:浮点数解析返回 0.0 会导致数据丢失
    // 更好的方案:使用 std::from_chars(但 C++20 中不是 constexpr)
    // 或者使用 C++23 的 constexpr 浮点解析(编译器支持有限)
    // 更实际的做法:如果浮点数解析不是必需的,可以拒绝浮点数
    // 或者使用整数类型表示浮点数(如缩放因子)
    
    // 方案1:返回错误
    // return std::nullopt;
    
    // 方案2:使用 long double 和自定义解析(复杂)
    // 以下为概念展示,实际需要实现完整解析器
    return JsonValue{std::stod(std::string(num_str))};  // 运行时!不是 constexpr!
} else {
            long long val = 0;
            auto [ptr, ec] = std::from_chars(num_str.data(), num_str.data() + num_str.size(), val);
            if (ec != std::errc()) return std::nullopt;
            return JsonValue{val};
        }
    }

    constexpr std::optional<JsonValue> parse_array() {
        if (input[pos] != '[') return std::nullopt;
        ++pos;
        skip_whitespace();

        std::vector<JsonValue> arr;
        if (pos < input.size() && input[pos] == ']') {
            ++pos;
            return JsonValue{std::move(arr)};
        }

        while (true) {
            auto val = parse_value();
            if (!val) return std::nullopt;
            arr.push_back(std::move(*val));

            skip_whitespace();
            if (pos >= input.size()) return std::nullopt;
            if (input[pos] == ']') { ++pos; break; }
            if (input[pos] != ',') return std::nullopt;
            ++pos;
        }
        return JsonValue{std::move(arr)};
    }
};

// 顶层解析函数
constexpr std::optional<JsonValue> parse_json(std::string_view input) {
    JsonParser parser{input};
    return parser.parse_value();
}

// 编译期测试
constexpr bool test_json_parser() {
    // 解析简单值
    auto null_val = parse_json("null");
    if (!null_val || !null_val->is_null()) return false;

    auto bool_val = parse_json("true");
    if (!bool_val || !bool_val->is_bool() || !bool_val->as_bool()) return false;

    auto int_val = parse_json("42");
    if (!int_val || !int_val->is_int() || int_val->as_int() != 42) return false;

    auto str_val = parse_json("\"hello\"");
    if (!str_val || !str_val->is_string() || str_val->as_string() != "hello") return false;

    // 解析数组
    auto arr_val = parse_json("[1, 2, 3]");
    if (!arr_val || !arr_val->is_array()) return false;
    auto& arr = std::get<std::vector<JsonValue>>(arr_val->data);
    if (arr.size() != 3) return false;
    if (arr[0].as_int() != 1 || arr[1].as_int() != 2 || arr[2].as_int() != 3) return false;

    // 解析嵌套数组
    auto nested = parse_json("[[1, 2], [3, 4]]");
    if (!nested || !nested->is_array()) return false;

    return true;
}

// 编译期验证 JSON 解析器正确性
static_assert(test_json_parser(), "JSON parser failed at compile time!");

int main() {
    // 运行时使用同一个解析器
    auto result = parse_json(R"([10, 20, 30, 40, 50])");
    if (result && result->is_array()) {
        auto& arr = std::get<std::vector<JsonValue>>(result->data);
        std::cout << "Parsed array with " << arr.size() << " elements: ";
        for (const auto& v : arr) {
            std::cout << v.as_int() << " ";
        }
        std::cout << "\n";
    }

    // 编译期已验证解析器正确性
    std::cout << "Compile-time JSON parser tests: PASSED\n";

    return 0;
}

实战三:编译期配置系统(Qt 应用场景)

这是将 constexpr 能力与 Qt 应用配置解析结合的实战案例:

#include <string_view>
#include <array>
#include <algorithm>
#include <iostream>
#include <optional>

// ============================================================
// 编译期配置系统:在编译时将配置字符串解析为结构化数据
// ============================================================

// 配置条目
struct ConfigEntry {
    std::string_view key;
    std::string_view value;
};

// 配置集合(编译期固定大小)
template<size_t N>
struct ConfigSet {
    std::array<ConfigEntry, N> entries;

    constexpr std::optional<std::string_view> get(std::string_view key) const {
        for (const auto& e : entries) {
            if (e.key == key) return e.value;
        }
        return std::nullopt;
    }

    constexpr int get_int(std::string_view key, int default_val = 0) const {
        auto val = get(key);
        if (!val) return default_val;

        // 编译期字符串转 int
        int result = 0;
        bool negative = false;
        size_t i = 0;
        if (!val->empty() && (*val)[0] == '-') {
            negative = true;
            i = 1;
        }
        for (; i < val->size(); ++i) {
            if ((*val)[i] < '0' || (*val)[i] > '9') return default_val;
            result = result * 10 + ((*val)[i] - '0');
        }
        return negative ? -result : result;
    }

    constexpr bool get_bool(std::string_view key, bool default_val = false) const {
        auto val = get(key);
        if (!val) return default_val;
        if (*val == "true" || *val == "1" || *val == "yes") return true;
        if (*val == "false" || *val == "0" || *val == "no") return false;
        return default_val;
    }
};

// 辅助:编译期字符串分割器
// 将 "key1=value1\nkey2=value2\n" 解析为 ConfigEntry 数组
template<size_t MaxEntries>
constexpr auto parse_config(std::string_view raw) {
    struct ParseResult {
        std::array<ConfigEntry, MaxEntries> entries{};
        size_t count = 0;
    };

    ParseResult result;
    size_t pos = 0;

    while (pos < raw.size() && result.count < MaxEntries) {
        // 找到行尾
        // ✅ std::string_view::find 在 C++17 起就是 constexpr 可用的
// 所以上述代码在 constexpr 上下文中是合法的
        size_t line_end = raw.find('\n', pos);
        if (line_end == std::string_view::npos) line_end = raw.size();

        std::string_view line = raw.substr(pos, line_end - pos);
        pos = line_end + 1;

        // 跳过空行和注释
        if (line.empty() || line[0] == '#') continue;

        // 找到 '=' 分隔符
        size_t eq_pos = line.find('=');
        if (eq_pos == std::string_view::npos) continue;

        // 提取 key 和 value,去除首尾空格
        std::string_view key = line.substr(0, eq_pos);
        std::string_view value = line.substr(eq_pos + 1);

        // trim
        while (!key.empty() && key.back() == ' ') key.remove_suffix(1);
        while (!key.empty() && key.front() == ' ') key.remove_prefix(1);
        while (!value.empty() && value.back() == ' ') value.remove_suffix(1);
        while (!value.empty() && value.front() == ' ') value.remove_prefix(1);

        result.entries[result.count++] = {key, value};
    }

    return result;
}

// ============================================================
// 实际使用:在编译期解析应用配置
// ============================================================

// 应用配置的原始字符串(可以来自 #include 的文件内容、宏定义等)
// 在实际 Qt 项目中,这可以来自 qrc 资源文件的内容
constexpr std::string_view APP_CONFIG_RAW = R"(
# Application Configuration
# Parsed at compile time!

app_name = MyApp
app_version = 2
window_width = 1920
window_height = 1080
max_connections = 100
enable_debug = false
theme = dark
log_level = 3
)";

// 编译期解析!
constexpr auto parsed = parse_config<16>(APP_CONFIG_RAW);
constexpr ConfigSet<parsed.count> app_config{parsed.entries};

// 编译期验证配置值
static_assert(app_config.get_int("window_width") == 1920);
static_assert(app_config.get_int("window_height") == 1080);
static_assert(app_config.get_bool("enable_debug") == false);
static_assert(app_config.get_int("max_connections") == 100);

// Qt 场景:启动时零成本加载配置
/*
// 在 Qt 应用中的使用方式:

class AppConfig {
public:
    // 所有配置值在编译时就已确定
    static constexpr auto name() { return "MyApp"; }
    static constexpr auto version() { return 2; }

    struct WindowConfig {
        static constexpr int width = 1920;
        static constexpr int height = 1080;
    };

    // 运行时使用
    void applyToWindow(QMainWindow* window) const {
        window->resize(WindowConfig::width, WindowConfig::height);
        window->setWindowTitle(QString("%1 v%2")
            .arg(name()).arg(version()));
    }

    // 零运行时开销的配置获取
    static constexpr int maxConnections() {
        return app_config.get_int("max_connections");
    }
};

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);

    QMainWindow window;
    AppConfig config;
    config.applyToWindow(&window);  // 使用编译期配置值

    // 编译期确定的值可以直接用于模板参数、数组大小等
    std::array<int, AppConfig::maxConnections()> connection_pool;
    // ...
}
*/

// ============================================================
// 进阶:编译期配置验证
// ============================================================

template<size_t N>
constexpr bool validate_config(const ConfigSet<N>& config) {
    // 验证必要字段存在
    auto app_name = config.get("app_name");
if (!app_name || app_name->empty()) return false;
// 验证 app_name 不为空字符串

    // 验证数值范围
    int width = config.get_int("window_width");
    if (width < 320 || width > 7680) return false;

    int height = config.get_int("window_height");
    if (height < 240 || height > 4320) return false;

    int max_conn = config.get_int("max_connections");
    if (max_conn < 1 || max_conn > 10000) return false;

    return true;
}

// 编译期验证配置合法性!
static_assert(validate_config(app_config), "Invalid application configuration!");

int main() {
    std::cout << "=== Compile-time Configuration System ===\n\n";

    std::cout << "Configuration entries (" << parsed.count << " total):\n";
    for (size_t i = 0; i < parsed.count; ++i) {
        std::cout << "  " << app_config.entries[i].key
                  << " = " << app_config.entries[i].value << "\n";
    }

    std::cout << "\nTyped access:\n";
    std::cout << "  Window: " << app_config.get_int("window_width")
              << "x" << app_config.get_int("window_height") << "\n";
    std::cout << "  Debug: " << (app_config.get_bool("enable_debug") ? "on" : "off") << "\n";
    std::cout << "  Max connections: " << app_config.get_int("max_connections") << "\n";

    // 这些值在编译时就已确定,零运行时开销
    constexpr int w = app_config.get_int("window_width");
    constexpr int h = app_config.get_int("window_height");
    std::cout << "\n  Compile-time constants: " << w << "x" << h << "\n";

    std::cout << "\nAll compile-time assertions PASSED!\n";

    return 0;
}

实战四:constexpr 与模板元编程结合

#include <array>
#include <type_traits>
#include <iostream>
#include <cstddef>

// === 编译期类型查找表 ===

// 类型信息结构
struct TypeInfo {
    const char* name;
    size_t size;
    size_t alignment;
    bool is_trivially_copyable;
};

// 模板元编程 + constexpr:生成类型查找表
template<typename... Ts>
struct TypeRegistry {
    static constexpr size_t count = sizeof...(Ts);

    static constexpr auto build_table() {
        std::array<TypeInfo, count> table{};
        size_t i = 0;
        // 使用折叠表达式展开参数包
        ((table[i++] = TypeInfo{
            typeid(Ts).name(),
            sizeof(Ts),
            alignof(Ts),
            std::is_trivially_copyable_v<Ts>
        }), ...);
        // ❌ 严重错误:typeid 在 constexpr 上下文中不可用!
// typeid 是运行时操作,不能在 constexpr 函数中使用

// 解决方案1:使用 __PRETTY_FUNCTION__ 编译器扩展(非标准)
// 但 __PRETTY_FUNCTION__ 在 constexpr 中可能可用
constexpr const char* get_type_name() {
    return __PRETTY_FUNCTION__;  // 编译器扩展
}

// 解决方案2:手动维护类型名称映射(需要显式注册)
template<typename T>
struct TypeName {
    static constexpr const char* name = "unknown";
};

template<>
struct TypeName<int> {
    static constexpr const char* name = "int";
};

template<>
struct TypeName<double> {
    static constexpr const char* name = "double";
};

// 解决方案3:使用 C++26 的编译期反射(未来)
        return table;
    }

    static constexpr auto table = build_table();
};

// 编译期计算排列组合
constexpr size_t factorial(size_t n) {
    size_t result = 1;
    for (size_t i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

// 编译期生成组合数表(杨辉三角)
template<size_t N>
constexpr auto build_pascal_triangle() {
    std::array<std::array<size_t, N>, N> triangle{};
    for (size_t i = 0; i < N; ++i) {
        triangle[i][0] = 1;
        for (size_t j = 1; j <= i; ++j) {
            triangle[i][j] = triangle[i-1][j-1] +
                             (j < i ? triangle[i-1][j] : 0);
        }
    }
    return triangle;
}

constexpr auto pascal = build_pascal_triangle<10>();

// 编译期生成查找表用于哈希函数优化
// 场景:为 Qt 的 QHash 生成完美哈希的查找表
template<size_t TableSize>
constexpr auto build_hash_lut() {
    std::array<size_t, TableSize> lut{};
    for (size_t i = 0; i < TableSize; ++i) {
        // ✅ 模运算在 constexpr 中可用
// 注意:2654435761u 是 golden ratio 的近似值(常用于哈希)
// 编译期计算 lut 表,零运行时开销
lut[i] = (i * 2654435761u) % TableSize;
    }
    return lut;
}

constexpr auto hash_lut = build_hash_lut<256>();

// 编译期状态机
enum class Event { Start, Stop, Pause, Resume, Reset };
enum class State { Idle, Running, Paused, Error };

struct Transition {
    State from = State::Idle;
    Event event = Event::Start;
    State to = State::Idle;
    bool valid = false;  // 默认为 invalid
};

constexpr auto build_state_machine() {
    constexpr size_t N_STATES = 4;
    constexpr size_t N_EVENTS = 5;
    std::array<std::array<Transition, N_EVENTS>, N_STATES> sm{};

    auto set = [&](State from, Event evt, State to) {
        sm[static_cast<int>(from)][static_cast<int>(evt)] =
            Transition{from, evt, to, true};
    };

    // 定义合法转换
    set(State::Idle,    Event::Start,  State::Running);
    set(State::Idle,    Event::Reset,  State::Idle);
    set(State::Running, Event::Stop,   State::Idle);
    set(State::Running, Event::Pause,  State::Paused);
    set(State::Running, Event::Reset,  State::Idle);
    set(State::Paused,  Event::Resume, State::Running);
    set(State::Paused,  Event::Stop,   State::Idle);
    set(State::Paused,  Event::Reset,  State::Idle);
    set(State::Error,   Event::Reset,  State::Idle);

    return sm;
}

constexpr auto state_machine = build_state_machine();

// 编译期验证状态机
constexpr bool validate_state_machine() {
    // 每个状态至少有一个合法转换
    for (size_t s = 0; s < 4; ++s) {
        bool has_transition = false;
        for (size_t e = 0; e < 5; ++e) {
            if (state_machine[s][e].valid) {
                has_transition = true;
                break;
            }
        }
        if (!has_transition) return false;
    }
    return true;
}

static_assert(validate_state_machine(), "State machine has dead states!");

int main() {
    std::cout << "=== Constexpr + Template Metaprogramming ===\n\n";

    // 杨辉三角
    std::cout << "Pascal's Triangle (10 rows):\n";
    for (size_t i = 0; i < 10; ++i) {
        for (size_t j = 0; j <= i; ++j) {
            std::cout << pascal[i][j] << " ";
        }
        std::cout << "\n";
    }

    // 阶乘
    std::cout << "\nFactorials:\n";
    for (size_t i = 0; i <= 12; ++i) {
        std::cout << "  " << i << "! = " << factorial(i) << "\n";
    }

    // 状态机运行时使用
    std::cout << "\nState Machine transitions:\n";
    const char* state_names[] = {"Idle", "Running", "Paused", "Error"};
    const char* event_names[] = {"Start", "Stop", "Pause", "Resume", "Reset"};

    for (size_t s = 0; s < 4; ++s) {
        for (size_t e = 0; e < 5; ++e) {
            if (state_machine[s][e].valid) {
                std::cout << "  " << state_names[s] << " + "
                          << event_names[e] << " -> "
                          << state_names[static_cast<int>(state_machine[s][e].to)]
                          << "\n";
            }
        }
    }

    return 0;
}

常见陷阱与最佳实践

陷阱 1:constexpr vector 的生命周期

// 错误:constexpr vector 在编译期分配内存,必须在编译期释放
constexpr int bad_example() {
    std::vector<int> v = {1, 2, 3};
    return v[0];
    // v 在这里析构——如果函数在编译期求值,
    // 编译器会在编译期分配和释放内存,这是 OK 的
    // 但如果你试图把 vector 返回出去就不行了
}

// 正确:将 vector 内容转移到固定大小的容器中
constexpr auto good_example() {
    std::vector<int> v = {1, 2, 3};
    // ... 处理 v ...
    std::array<int, 3> result{};
    for (size_t i = 0; i < v.size(); ++i) result[i] = v[i];
    return result;  // array 是字面量类型,可以返回
}

陷阱 2:consteval vs constexpr

// constexpr:可以在编译期或运行时求值
constexpr int flexible(int x) { return x * 2; }

// consteval:强制编译期求值,传入运行时值会编译错误
consteval int compile_time_only(int x) { return x * 2; }

void test() {
    int runtime_val = 21;
    // int d = compile_time_only(runtime_val); // ❌ 编译错误!
    // 错误信息:call to consteval function 'compile_time_only' is not a constant expression
    
    // ✅ 正确方式:传入编译期常量
    constexpr int val = 21;
    int d = compile_time_only(val);  // OK:编译期求值
    
    // ⚠️ 注意:变量名 runtime_val 虽然是"运行时"变量,
    // 但如果是 constexpr 的,就可以传给 consteval
    constexpr int also_const = 21;
    int e = compile_time_only(also_const);  // OK
}

最佳实践

  • 默认使用 constexpr,允许编译期和运行时双重使用
  • 只有当你确定函数必须在编译期求值时,才使用 consteval
  • if consteval(C++23)在函数内部区分求值时机

陷阱 3:编译期字符串处理的限制

// 编译期不能创建真正的 std::string(C++20 的 constexpr string 有限制)
// 优先使用 std::string_view 进行编译期字符串操作

// 错误:编译期 string 返回后析构问题
// constexpr std::string make_string() { ... }  // 可能有问题

// 正确:使用 string_view 引用字面量
constexpr std::string_view make_sv() {
    return "hello";  // 字面量生命周期是整个程序
}

最佳实践清单

  1. 结果转存为 std::array:编译期 vector/string 最终要转为固定大小容器
  2. 使用 consteval 强制编译期:对性能关键的查找表生成使用 consteval
  3. static_assert 验证:对编译期计算结果添加断言,提前发现错误
  4. 避免过度复杂化:编译期计算虽强,但不要为了"酷"而在编译期做运行时更合适的事
  5. 注意编译器限制:不同编译器对 constexpr 的支持程度不同,用 -std=c++20 并关注编译错误

进阶思考

Qt 中的编译期优化机会

场景传统做法constexpr 做法收益
配置文件解析启动时读取解析编译期解析嵌入二进制零启动开销
颜色主题表运行时加载 QSS编译期生成 std::array<QRgb, N> 或 std::array<std::array<int, 4>, N>`零运行时分配
协议消息映射运行时注册编译期构建完美哈希表O(1) 查找
UI 布局常量运行时计算编译期确定 constexpr 常量编译器内联优化
国际化字符串运行时加载 .qm编译期索引(结合 qrc)减少启动 IO

constexpr 与反射(C++26 展望)

C++26 预计将引入编译期反射(consteval reflection),届时可以:

  • 编译期遍历类的所有成员
  • 自动生成序列化/反序列化代码
  • 编译期生成 Qt 的元对象系统数据

参考资源


下一节预告:Day 18 深入类型擦除实战,手写一个类似 QVariant 的类型安全容器。

「LLM那些事」系列第 4 篇《上下文窗口的边界》,文章连接:https://blog.csdn.net/houwenjin/article/details/163999753。 演示什么:在「预测」Sheet 的黄色格子里输入一句话(默认「来泡一杯」),四个「模型」——分别只统计最后 1 / 2 / 3 / 4 个字的 n-gram 查表——同时预测下一个字。同一个输入,看的上下文越长,候选越少、预测越确定: ┌────────────────┬──────────┬───────────────┬──────┐ │ 只看最后几个字 │ 用的前缀 │ 候选下一字数 │ 预测 │ ├────────────────┼──────────┼───────────────┼──────┤ │ 1 个 │ 杯 │ 3(茶/子/水) │ 模糊 │ ├────────────────┼──────────┼───────────────┼──────┤ │ 2 个 │ 一杯 │ 2(茶/水) │ 收窄 │ ├────────────────┼──────────┼───────────────┼──────┤ │ 3 个 │ 泡一杯 │ 1(茶) │ 确定 │ ├────────────────┼──────────┼───────────────┼──────┤ │ 4 个 │ 来泡一杯 │ 1(茶) │ 确定 │ └────────────────┴──────────┴───────────────┴──────┘
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值