Day25-编译期计算与 constexpr if:跨越编译与运行的边界

编译期计算与 constexpr if:跨越编译与运行的边界

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

引言

C++ 编译器不仅仅是一个翻译工具,它更是一台可以执行计算的虚拟机。在现代 C++(特别是 C++17/20)中,编译期计算的能力已经发展到可以处理相当复杂的逻辑。理解编译期计算的边界与运行时的交互,是从"会用模板"到"精通元编程"的关键跃迁。

为什么这个知识点重要

在 Day 5 和 Day 17 的基础上,本篇将进一步深入探讨编译期计算的边界问题:

  1. 性能极致的追求:编译期计算将计算工作从运行时转移到编译时,零运行时开销
  2. 类型安全的保障:编译期计算天然与类型系统结合,提供编译时检查
  3. 设计模式的基石:许多现代设计模式(如 Policy-Based Design、Type Erasure)都依赖编译期分派

与前面内容的关联

  • Day 5constexpr 基础、函数限制与演进
  • Day 17constexpr 容器/算法、编译期 JSON 解析
  • Day 22-24:SFINAE、Type Traits、变参模板的编译期机制
  • Day 26(本计划):编译期计算是 Policy-Based Design 的核心支撑

核心概念

编译期计算的本质

C++ 的编译期计算经历了三个阶段的演进:

阶段 C++ 版本 关键技术 能力
黑暗时代 C++98/03 模板元编程、整型包装 仅限整数、极其繁琐
觉醒时代 C++11/14 constexpr 函数 可包含分支和循环
成熟时代 C++17/20 if constexpr、泛型 constexpr 控制流可见化

constexpr 函数的限制与边界

C++14 解锁了 constexpr 函数的几乎全部能力,但仍有硬性限制:

cpp 复制代码
// C++14 及以后,constexpr 函数可以包含:
// - 条件分支 (if/else, switch)
// - 循环 (for, while, do-while)
// - 递归
// - 变量声明

// 但仍然禁止:
// - try/catch 块(C++20 仅在特定条件下支持,C++23 进一步放宽)
//   注意:C++20 允许 constexpr 函数中使用 try-catch,但 catch 子句不能捕获异常
//   (因为异常不能在编译期传播),实际上仍然无法"处理"异常
// - asm 声明
// - goto 语句(本身就是坏味道)
// - 非 constexpr 函数的调用

// C++20 的关键突破:
// - 可以在 constexpr 函数中使用 dynamic_cast 和 typeid
// - 可以在 constexpr 函数中使用 new/delete
// - 可以使用 try-catch 块

编译期分派的两种范式

编译期分派是指在编译期根据类型信息选择不同代码路径的技术:

cpp 复制代码
// 范式一:Tag Dispatch(标签分派)
template<typename Iterator>
void advance_impl(Iterator& it, std::random_access_iterator_tag) {
    // 编译期分支:随机访问迭代器的 O(1) 实现
    it += 1;
}

template<typename Iterator>
void advance_impl(Iterator& it, std::input_iterator_tag) {
    // 编译期分支:输入迭代器的 O(n) 实现
    ++it;
}

template<typename Iterator>
void advance(Iterator& it) {
    // 编译期分派:通过迭代器类型标签的静态类型在编译期选择重载
    // 虽然语法上是在"运行时"传递参数,但 iterator_category 是编译期已知的
    advance_impl(it, typename Iterator::iterator_category{});
}

// 范式二:if constexpr(C++17)
template<typename T>
auto process(T val) {
    if constexpr (std::is_integral_v<T>) {
        return val * 2;  // 仅 T 为整数时编译
    } else if constexpr (std::is_floating_point_v<T>) {
        return val * 2.0;  // 仅 T 为浮点时编译
    } else {
        return val;  // 其他类型编译
    }
}

编译期与运行时的边界判断

判断一个表达式是否可以在编译期求值是理解 constexpr 的核心:

cpp 复制代码
// 编译器必须能够在编译期确定值的场景:
// 1. 模板参数(非类型模板参数)
// 2. constexpr 变量初始化
// 3. switch case 的 case 标签
// 4. 数组大小(如果不是 VLA)

// 编译期 vs 运行时:关键判断
constexpr int a = 10;           // 编译期
const int b = 10;               // 可能是编译期( linkage 有关)
int c = 10;                     // 运行时
std::array<int, a> arr1;       // OK:编译期
// std::array<int, c> arr2;    // ERROR:c 不是 constexpr

// 动态边界的危险
template<size_t N>
struct Wrapper {
    std::array<int, N> data;
};

// 模板参数保证了编译期性
Wrapper<42> w;  // OK

// 但运行时确定的大小不行
// size_t n = get_size();
// Wrapper<n> w2;  // ERROR

代码实战

实战一:编译期字符串处理

C++20 引入了 std::constexpr_string_view,使得编译期字符串处理成为可能:

cpp 复制代码
#include <array>
#include <cstddef>
#include <cstring>
#include <string_view>

// 编译期字符串哈希(FNV-1a 算法)
constexpr uint64_t fnv1a(std::string_view sv) {
    uint64_t hash = 0xcbf29ce484222325ULL;  // FNV offset basis
    for (unsigned char c : sv) {
        hash ^= c;
        hash *= 0x100000001b3ULL;  // FNV prime
    }
    return hash;
}

// 编译期字面量哈希
constexpr auto hash_foo = fnv1a("foo");  // 编译期计算
constexpr auto hash_bar = fnv1a("bar");

// 编译期字符串字面量(C++20)
template<size_t N>
struct CompileTimeString {
    char data[N];
    constexpr CompileTimeString(const char (&str)[N]) : data{} {
        // 复制字符串(包括末尾的 '\0')
        for (size_t i = 0; i < N; ++i)
            data[i] = str[i];
    }
    constexpr std::string_view view() const {
        // N - 1 去除末尾的 '\0',但如果字符串为空,N 为 1(只有 '\0')
        return std::string_view(data, N > 1 ? N - 1 : 0);
    }
    constexpr size_t length() const { return N > 1 ? N - 1 : 0; }
};

// 编译期模式匹配:字符串解析
template<size_t N>
template<size_t N>
constexpr int parse_int(const char (&str)[N]) {
    int result = 0;
    int sign = 1;
    size_t i = 0;
    
    // 空字符串检查
    if constexpr (N <= 1) {
        return 0;  // 空字符串返回 0
    }
    
    if (str[0] == '-') {
        sign = -1;
        i = 1;
    }
    
    // 如果只有 '-' 符号,返回 0
    if (i >= N - 1) return 0;
    
    for (; i < N - 1 && str[i] >= '0' && str[i] <= '9'; ++i) {
        result = result * 10 + (str[i] - '0');
    }
    
    return result * sign;
}

constexpr int config_value = parse_int("12345");  // 编译期计算:12345
static_assert(config_value == 12345);

// 编译期路由表
template<size_t Hash>
struct RouteHandler;

template<>
struct RouteHandler<fnv1a("/api/users")> {
    static constexpr auto path = "/api/users";
    static void handle() { /* ... */ }
};

template<>
struct RouteHandler<fnv1a("/api/posts")> {
    static constexpr auto path = "/api/posts";
    static void handle() { /* ... */ }
};

// 编译期路由分发
template<size_t N>
void route(const char (&path)[N]) {
	// ⚠️ 注意:constexpr 变量要求 path 在编译期已知
    // 如果 path 是运行时字符串,此代码无法编译!
    // 正确用法:path 必须是编译期字符串字面量
    constexpr auto hash = fnv1a(std::string_view(path, N - 1));
    
    if constexpr (hash == fnv1a("/api/users")) {
        RouteHandler<fnv1a("/api/users")>::handle();
    } else if constexpr (hash == fnv1a("/api/posts")) {
        RouteHandler<fnv1a("/api/posts")>::handle();
    } else {
        // 404
    }
}

实战二:编译期类型容器与查询

cpp 复制代码
#include <type_traits>
#include <array>
#include <utility>

// 编译期类型列表
template<typename... Ts>
struct TypeList {
    static constexpr size_t size = sizeof...(Ts);
};

// 编译期类型查找
template<typename List, typename T, size_t Idx = 0>
struct IndexOf;

template<typename T, size_t Idx>
struct IndexOf<TypeList<>, T, Idx> 
    : std::integral_constant<size_t, std::numeric_limits<size_t>::max()> {};

template<typename Head, typename... Tail, size_t Idx>
struct IndexOf<TypeList<Head, Tail...>, Head, Idx> 
    : std::integral_constant<size_t, Idx> {};

template<typename Head, typename... Tail, size_t Idx>
struct IndexOf<TypeList<Head, Tail...>, T, Idx> 
    : IndexOf<TypeList<Tail...>, T, Idx + 1> {};

// 编译期类型映射
template<typename List, template<typename> class Mapper>
struct MapTypes;

template<template<typename> class Mapper, typename... Ts>
struct MapTypes<TypeList<Ts...>, Mapper> {
    using type = TypeList<typename Mapper<Ts>::type...>;
};

// 编译期类型过滤
template<typename List, template<typename> class Pred>
struct FilterTypes;

template<template<typename> class Pred>
struct FilterTypes<TypeList<>, Pred> {
    using type = TypeList<>;
};

template<typename Head, typename... Tail, template<typename> class Pred>
struct FilterTypes<TypeList<Head, Tail...>, Pred> {
    using TailFiltered = typename FilterTypes<TypeList<Tail...>, Pred>::type;
    using type = std::conditional_t<
        Pred<Head>::value,
        TypeList<Head, TailFiltered>,  // ⚠️ 注意:这里不能展开 TailFiltered
        TailFiltered
    >;
};

// 示例:筛选出所有指针类型
template<typename T>
struct IsPointer : std::is_pointer<T> {};

using AllTypes = TypeList<int, int*, double, double*, char, void*>;
using PointerTypes = typename FilterTypes<AllTypes, IsPointer>::type;
// PointerTypes = TypeList<int*, double*, void*>

// 编译期函数表
template<typename Ret, typename... Args>
struct FunctionTable {
    template<size_t N>
    struct Entry {
        Ret (*func)(Args...);
        const char* name;
    };
    
    template<size_t N>
    constexpr Ret invoke(const char* name, Args... args) const {
        for (size_t i = 0; i < N; ++i) {
            bool match = true;
            for (size_t j = 0; entries[i].name[j] && name[j]; ++j) {
                if (entries[i].name[j] != name[j]) {
                    match = false;
                    break;
                }
            }
            // ⚠️ 注意:此函数是运行时查找,不是编译期
// name[0] == '\0' 是字符串结束标志,但此判断位置有误
// 正确逻辑应该是:如果匹配成功且已到达字符串末尾,才返回
// 这里需要遍历 entries 并比较完整字符串

// 修正后的实现:
for (size_t i = 0; i < N; ++i) {
    // 使用 strcmp 或手动比较
    const char* entry_name = entries[i].name;
    const char* target = name;
    while (*entry_name && *target && *entry_name == *target) {
        ++entry_name;
        ++target;
    }
    if (*entry_name == '\0' && *target == '\0') {
        return entries[i].func(args...);
    }
}
        }
        throw std::runtime_error("Function not found");
    }
    
    std::array<Entry<N>, N> entries;
};

// 编译期数学计算
template<unsigned N>
struct Factorial : std::integral_constant<unsigned, N * Factorial<N - 1>::value> {};

template<>
struct Factorial<0> : std::integral_constant<unsigned, 1> {};

template<unsigned N>
constexpr unsigned factorial_v = Factorial<N>::value;

static_assert(factorial_v<10> == 3628800);

// 编译期斐波那契(使用折叠表达式)
template<size_t N>
struct Fibonacci : std::integral_constant<size_t, 
    Fibonacci<N - 1>::value + Fibonacci<N - 2>::value> {};

template<>
struct Fibonacci<0> : std::integral_constant<size_t, 0> {};

template<>
struct Fibonacci<1> : std::integral_constant<size_t, 1> {};

// 编译期数组操作
template<size_t N>
constexpr std::array<int, N> make_fibonacci_array() {
    std::array<int, N> arr{};
    if constexpr (N >= 1) arr[0] = 0;
    if constexpr (N >= 2) arr[1] = 1;
    if constexpr (N > 2) {
        for (size_t i = 2; i < N; ++i) {
            arr[i] = arr[i - 1] + arr[i - 2];
        }
    }
    return arr;
}

constexpr auto fib_table = make_fibonacci_array<20>();
static_assert(fib_table[19] == 4181);  // 第20个斐波那契数

实战三:if constexpr 与编译期分支

if constexpr 是 C++17 引入的关键特性,它将分支条件的评估移至编译期:

cpp 复制代码
#include <variant>
#include <string>
#include <type_traits>
#include <iostream>

// 经典场景:递归结构的打印
struct JsonValue {
    std::variant<
        std::monostate,
        bool,
        int64_t,
        double,
        std::string,
        std::vector<JsonValue>,
        std::vector<std::pair<std::string, JsonValue>>
    > data;
};

void print_json(const JsonValue& jv, int indent = 0);

// if constexpr 的优雅实现
void print_json(const JsonValue& jv, int indent) {
    std::visit([indent](const auto& v) {
        using T = std::decay_t<decltype(v)>;
        
        if constexpr (std::is_same_v<T, std::monostate>) {
            std::cout << "null";
        } else if constexpr (std::is_same_v<T, bool>) {
            std::cout << (v ? "true" : "false");
        } else if constexpr (std::is_same_v<T, int64_t>) {
            std::cout << v;
        } else if constexpr (std::is_same_v<T, double>) {
            std::cout << v;
        } else if constexpr (std::is_same_v<T, std::string>) {
            std::cout << "\"" << v << "\"";
        } else if constexpr (std::is_same_v<T, std::vector<JsonValue>>) {
            std::cout << "[\n";
            for (size_t i = 0; i < v.size(); ++i) {
                for (int j = 0; j < indent + 2; ++j) std::cout << " ";
                print_json(v[i], indent + 2);
                if (i < v.size() - 1) std::cout << ",";
                std::cout << "\n";
            }
            for (int j = 0; j < indent; ++j) std::cout << " ";
            std::cout << "]";
        } else if constexpr (std::is_same_v<T, 
            std::vector<std::pair<std::string, JsonValue>>>) {
            std::cout << "{\n";
            for (size_t i = 0; i < v.size(); ++i) {
                for (int j = 0; j < indent + 2; ++j) std::cout << " ";
                std::cout << "\"" << v[i].first << "\": ";
                print_json(v[i].second, indent + 2);
                if (i < v.size() - 1) std::cout << ",";
                std::cout << "\n";
            }
            for (int j = 0; j < indent; ++j) std::cout << " ";
            std::cout << "}";
        }
    }, jv.data);
}

// 对比:传统 SFINAE 实现的繁琐
template<typename T, typename = void>
struct can_print : std::false_type {};

template<typename T>
struct can_print<T, std::void_t<
    decltype(std::declval<T>().print())
>> : std::true_type {};

// 异构容器的类型安全访问
template<typename T>
auto get_value(const std::variant<int, double, std::string>& v) -> T {
    if constexpr (std::is_same_v<T, int>) {
        return std::get<int>(v);
    } else if constexpr (std::is_same_v<T, double>) {
        return std::get<double>(v);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return std::get<std::string>(v);
    } else {
        // ⚠️ 注意:static_assert 在 else 分支中总是触发
        // 但 if constexpr 的 else 分支在条件为 false 时不会实例化
        // 所以 static_assert 只在 T 不匹配时触发
        static_assert(std::is_same_v<T, void>, "Unsupported type");
    }
}

实战四:编译期与运行时混合计算

理解编译期计算和运行时计算的边界以及如何混合使用:

cpp 复制代码
#include <array>
#include <iostream>
#include <chrono>

// 场景:编译期生成查找表,运行时查找
template<size_t N>
constexpr std::array<int, N> generate_lookup_table() {
    std::array<int, N> table{};
    for (size_t i = 0; i < N; ++i) {
        table[i] = static_cast<int>(i * i % 1000);  // 某种预计算
    }
    return table;
}

constexpr auto lookup = generate_lookup_table<1000>();  // 编译期生成

int runtime_lookup(int key) {
    if (key >= 0 && key < 1000) {
        return lookup[key];  // O(1) 查找
    }
    return -1;
}

// 编译期验证,运行时执行
template<int N>
struct PrimeSieve {
    static constexpr bool is_prime() {
        if (N < 2) return false;
        for (int i = 2; i * i <= N; ++i) {
            if (N % i == 0) return false;
        }
        return true;
    }
};

template<int N>
constexpr bool is_prime_v = PrimeSieve<N>::is_prime();

// 编译期验证质数
static_assert(is_prime_v<2>);
static_assert(is_prime_v<3>);
static_assert(is_prime_v<17>);
static_assert(is_prime_v<97>);
static_assert(!is_prime_v<100>);
static_assert(!is_prime_v<49>);

// 混合计算:编译期参数 + 运行时数据
template<size_t MaxSize>
class DynamicProcessor {
public:
    static constexpr size_t max_size = MaxSize;  // 编译期边界
    
    void process(int* data, size_t actual_size) {
        // 运行时边界检查,但使用编译期最大限制
        if (actual_size > MaxSize) {
            throw std::out_of_range("Exceeds compile-time maximum");
        }
        
        // 编译期展开的循环(对于小数据)
        if constexpr (MaxSize <= 64) {
            for (size_t i = 0; i < actual_size; ++i) {
                data[i] = transform(data[i]);
            }
        } else {
            // 大数据使用 SIMD 友好的版本
            for (size_t i = 0; i < actual_size; ++i) {
                data[i] = transform(data[i]);
            }
        }
    }
    
private:
    static constexpr int transform(int x) {
        return x * 2 + 1;  // 简单的编译期可计算的变换
    }
};

// 测试
int main() {
    // 运行时查找
    std::cout << "lookup[42] = " << runtime_lookup(42) << "\n";
    
    // 异构处理器
    DynamicProcessor<100> proc;
    std::array<int, 50> data;
    for (int& x : data) x = 1;
    proc.process(data.data(), 50);
    
    return 0;
}

常见陷阱与最佳实践

陷阱 1:constexpr 的隐式陷阱

cpp 复制代码
// 陷阱:看似 constexpr 但实际不是
const int get_value() { return 42; }  // 不是 constexpr!
const int table[] = {1, 2, get_value()};  // 运行时初始化

// 正确做法
constexpr int get_value_constexpr() { return 42; }
constexpr int table2[] = {1, 2, get_value_constexpr()};  // 编译期

// 注意:const 不保证编译期计算,只有 constexpr 保证

陷阱 2:递归深度限制

cpp 复制代码
// 陷阱:递归深度超过编译器限制
template<size_t N>
struct DeepRecursion : DeepRecursion<N - 1> {
    static constexpr size_t value = N + DeepRecursion<N - 1>::value;
};
template<>
struct DeepRecursion<0> {
    static constexpr size_t value = 0;
};

// DeepRecursion<10000> 可能导致编译器崩溃或内存溢出

// 最佳实践:使用迭代版本
template<size_t N>
struct Sum {
    static constexpr size_t compute() {
        size_t result = 0;
        for (size_t i = 1; i <= N; ++i) {
            result += i;
        }
        return result;
    }
    static constexpr size_t value = compute();
};

// 或限制递归深度
template<size_t N, size_t Limit = 1000>
struct SafeRecursion 
    : std::integral_constant<size_t, 
        (N <= Limit) ? (N + SafeRecursion<N - 1, Limit>::value) : N> {};

陷阱 3:引用类型与 constexpr

cpp 复制代码
// 陷阱:引用在编译期的问题
constexpr int& bad_ref = some_global;  // 全局变量的地址在编译期不确定

// 陷阱:字符串字面量的处理
// ❌ 错误:字符串字面量是 const char[N],不能赋值给 char*
// constexpr char* str = "hello";  // 类型不匹配!

// ✅ 正确:使用 const char* 或数组
constexpr const char* str_ptr = "hello";  // ✅ C++20 支持
constexpr char str_arr[] = "hello";       // ✅ 总是支持
constexpr std::string_view sv = "hello";  // ✅ C++20

// ⚠️ 注意:即使是 C++20,字符串字面量作为非类型模板参数
// 也需要使用自定义字面量类(如 std::basic_fixed_string)

// 正确做法:使用数组或 string_view
constexpr char str[] = "hello";  // OK
constexpr std::string_view sv = "hello";  // C++20 OK

// 陷阱:mutable 成员
// ⚠️ 问题:mutable 成员在 constexpr 函数中修改时
// 当函数在编译期求值时,修改发生在编译期,不会影响运行时对象
// 但如果同一个对象在运行时使用,cache_ 可能处于不一致状态

class Calculator {
    mutable int cache_ = 0;  // 初始化为 0
public:
    constexpr int compute(int x) const {
        // ⚠️ 危险:在 const 成员中修改 mutable 成员
        // 这违反了 const 的语义保证
        cache_ = x * 2;
        return cache_;
    }
};

// 更好的做法:不使用 mutable,而是返回计算结果
class BetterCalculator {
public:
    constexpr int compute(int x) const {
        return x * 2;  // 无状态
    }
};

陷阱 4:类模板的 constexpr 成员函数

cpp 复制代码
// 陷阱:类模板的 constexpr 函数实例化问题
template<typename T>
struct Wrapper {
    T value;
    
    constexpr T get() const {
        return value;
    }
    
    constexpr void set(T v) {  // C++20 之前非 constexpr
        value = v;
    }
};

// C++17 及之前,set() 不是 constexpr
// C++20 放宽了限制

// 最佳实践:明确标注并使用条件编译
template<typename T>
struct Wrapper {
    T value;
    
    constexpr T get() const { return value; }
    
// ⚠️ 注意:__cplusplus 的值因编译器而异
// GCC 10+ 在 C++20 模式下定义为 202002L
// MSVC 可能定义为 202004L 或更高
// 更可靠的方式是使用 __has_cpp_attribute 或编译器特定宏

// 可靠的方式:
#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
    constexpr void set(T v) { value = std::move(v); }
#endif
};

最佳实践总结

实践 原因
static_assert 验证编译期计算 编译期反馈错误,而不是运行时
优先使用 if constexpr 更清晰的代码,更好的错误信息
限制递归深度 防止编译器崩溃
分离编译期/运行时计算 查找表模式,性能最佳
使用 std::integral_constant 标准化的编译期常量表达
避免过度元编程 可读性优先,必要时使用宏

进阶思考

编译期正则表达式(C++20)

C++20 引入了 <regex> 的 constexpr 支持,使得编译期正则成为可能:

cpp 复制代码
// ⚠️ 警告:截至 2024 年,std::regex 的 constexpr 支持极其有限
// 大多数编译器尚未实现 constexpr 正则表达式
// 以下代码仅为概念展示,实际不可用

#include <regex>

// 实际上,std::regex 的 constexpr 构造函数在 C++20 中并未完全实现
// 建议使用编译期字符串解析或第三方库(如 CTRE)
// constexpr bool validate_email(std::string_view email) {
//     // 使用 CTRE 或其他编译期正则库
//     return true;  // 占位
// }
static_assert(validate_email("test@example.com"));
static_assert(!validate_email("invalid"));

编译期 Lambda(C++20)

C++20 的 unevaluated lambda 允许在模板参数中使用 Lambda:

cpp 复制代码
// 编译期数值计算
template<auto Fn>
struct Invoker {
    template<typename... Args>
    static constexpr auto call(Args&&... args) {
        return Fn(std::forward<Args>(args)...);
    }
};

// 使用
constexpr auto result = Invoker<[](int x) { return x * 2; }>::call(21);
static_assert(result == 42);

与 Type Traits 的深度结合

编译期计算与 Type Traits 的结合是元编程的核心:

cpp 复制代码
// 检测表达式有效性
template<typename, typename = void>
struct HasBegin : std::false_type {};

template<typename T>
struct HasBegin<T, std::void_t<decltype(std::declval<T>().begin())>>
    : std::true_type {};

// 检测并根据类型分发
template<typename T>
auto process_container(T& container) {
    if constexpr (HasBegin<T>::value) {
        // 容器有 begin()
        for (auto& elem : container) {
            // ...
        }
    } else {
        // 裸指针或数组
        // ...
    }
}

与 Concepts 的协同

C++20 Concepts 提供了更优雅的约束表达:

cpp 复制代码
template<typename T>
concept Iterable = requires(T t) {
    t.begin();
    t.end();
};

template<Iterable T>
void process(T& t) {
    if constexpr (std::ranges::sized_range<T>) {
        // C++20 ranges 的约束
    }
}

参考资源

标准文档

书籍与文章

  • 《C++ Templates: The Complete Guide》 - Vandevoorde & Josuttis:模板元编程的圣经
  • 《Modern C++ Design》 - Andrei Alexandrescu:Policy-Based Design 的开创性著作
  • **Fluent C++ - "The Compilation Time Magic of constexpr"](https://www.fluentcplusplus.com/constexpr/**: 编译期计算的实用指南

工具与实践

相关推荐
小小龙学IT1 小时前
第七节 C++ 与 QML 交互
开发语言·qt
晓晓_za8986681 小时前
Geo 优化源码二次开发:自定义地域规则改造实操文档
java·开发语言·搜索引擎·ci/cd·矩阵
ShineWinsu2 小时前
对于TRAE中配置Qt的解析
开发语言·c++·ide·vscode·qt·ai·trae
wy3136228212 小时前
Git——ignore让项目的 target 目录真正被忽略(忽略其他文件也是同样的道理)
开发语言·git
格林威2 小时前
C#图像分块处理:图像按行或按块(Tile)切分,多个 CPU 核心同时处理不同的区域
开发语言·图像处理·人工智能·机器学习·计算机视觉·c#·工业相机
leisoo80972 小时前
财报舞弊预警系统实战用Python挖掘应收存货异常因子 IG50免费开源股票数据API接口
开发语言·python
君顾12 小时前
AI新零售线上商城系统实战:架构设计与开发全流程指南
java·开发语言·零售
wind100322 小时前
Outdated Visual C++ Redistributable 过时 VC++ 运行库报错修复
开发语言·c++
吾皇斯巴达3 小时前
O_DIRECT与gcsfuse的go程序中实现内存页面对齐
开发语言·后端·golang