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++17 | constexpr lambda | 编译期 lambda 表达式 |
| C++20 | consteval + 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"; // 字面量生命周期是整个程序
}
最佳实践清单
- 结果转存为
std::array:编译期 vector/string 最终要转为固定大小容器 - 使用
consteval强制编译期:对性能关键的查找表生成使用consteval static_assert验证:对编译期计算结果添加断言,提前发现错误- 避免过度复杂化:编译期计算虽强,但不要为了"酷"而在编译期做运行时更合适的事
- 注意编译器限制:不同编译器对 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 的元对象系统数据
参考资源
- C++ Reference: constexpr
- C++ Reference: consteval
- C++20 constexpr dynamic allocation — Bartek’s Coding Adventures
- Compile-time JSON parsing — Vittorio Romeo 关于启动时间优化
- C++20 constexpr virtual functions — Barry Revzin
- Barry Revzin: What is a literal type?
下一节预告:Day 18 深入类型擦除实战,手写一个类似 QVariant 的类型安全容器。

262

被折叠的 条评论
为什么被折叠?



