C++反射与元编程
编译期反射与代码生成
前置知识
- C++与 WebAssembly:建议先完成前一篇的学习
学习目标
- 掌握「Concepts(C++20)」的核心机制、典型用法与常见陷阱
- 掌握「概述」的核心机制、典型用法与常见陷阱
- 掌握「历史动机与背景」的核心机制、典型用法与常见陷阱
- 掌握「形式化定义」的核心机制、典型用法与常见陷阱
- 掌握「理论推导」的核心机制、典型用法与常见陷阱
阅读建议:反射与元编程为【进阶原理】,建议先掌握模板与类型特征。
Concepts(C++20)
基本写法:requires 表达式
requires(<参数>) { <表达式>; }
// 编译期约束
template <typename T>
concept Iterable = requires(T t) {
t.begin();
t.end();
{ t.size() } -> std::convertible_to<size_t>;
};
template <Iterable T>
void printAll(const T& container) { /* ... */ }
基本写法:requires 子句
requires <概念>
// 函数模板约束
template <typename T>
requires std::integral<T>
T gcd(T a, T b) {
while (b) { T t = b; b = a % b; a = t; }
return a;
}
概述
反射(Reflection)是指在运行时或编译期获取程序结构信息(如类型名称、成员变量、成员函数、基类、模板参数等)并据此进行动态操作的能力。它是 Java、C#、Python 等语言的核心特性——通过 Class 对象,开发者可以在运行期枚举类的所有成员、调用任意方法、构造任意实例。这种能力支撑了这些语言丰富的生态:JPA/Hibernate ORM、Spring 依赖注入、Jackson/Gson 序列化、JUnit 测试框架等都依赖反射。
C++ 长期以来缺乏语言级别的反射支持。这是 C++ 设计哲学的必然结果:Bjarne Stroustrup 坚持零开销原则(zero-overhead principle)与静态类型哲学,认为运行期反射的代价(额外的元数据存储、类型擦除开销、JIT 不可达)与 C++ 的核心目标相违背。然而,C++ 生态同样需要 ORM、序列化、依赖注入、RPC 等基础设施,于是开发者们在过去三十年里发明了形形色色的”反射模拟”方案:
- 运行时类型信息(RTTI):
std::type_info、typeid、dynamic_cast,提供最小化的运行期类型识别; - 宏注册反射:通过
X-macro、BOOST_DESCRIBE、REFL_AUTO等宏手动注册类成员; - 模板元编程(TMP):利用 SFINAE、
if constexpr、Concepts 在编译期生成类型相关的代码; - 外部代码生成:通过 Protobuf、FlatBuffers、Cap’n Proto 等工具从 IDL 生成反射代码;
- 静态反射提案(C++26 P2996):引入
^^T反射运算符与std::meta::info类型,提供语言级编译期反射。
元编程(Metaprogramming)是更广泛的概念——通过程序来生成或变换程序。模板元编程是 C++ 的图灵完备的元编程语言,可以在编译期执行任意计算。反射与元编程密切相关:反射获取类型信息,元编程基于这些信息生成代码。
C++26 草案引入的静态反射(P2996)是 C++ 历史上最重要的特性之一,它将彻底改变 C++ 的元编程生态。本章将从反射与元编程的基本概念出发,深入探讨各种实现策略、形式化语义、工程实践与陷阱,并展望 C++26 反射带来的革命性变化。
历史动机与背景
反射的语言谱系
不同语言对反射的支持程度差异巨大:
| 语言 | 反射类型 | 性能开销 | 元数据存储 | 典型应用 |
|---|---|---|---|---|
| Java | 运行时完整反射 | 较高(JIT 优化) | 每类元数据(数十 KB) | Spring/Hibernate/Jackson |
| C# | 运行时完整反射 | 中等 | CLR 元数据 | ASP.NET/EF/NUnit |
| Python | 运行时完整反射 | 极高(解释执行) | 对象 dict | Django/SQLAlchemy |
| Go | 运行时结构体反射 | 中等 | 类型描述符 | encoding/json |
| Rust | 编译期派生 + 运行时Any | 零开销(编译期) | derive 宏 | serde |
| C++(现状) | RTTI + 宏 + TMP | 零(编译期)/ 中(RTTI) | 不定 | 各类框架 |
| C++(C++26 草案) | 静态反射 | 零开销(编译期) | 反射值 | 标准化框架 |
C++ 反射的早期尝试
C++ 反射的探索可追溯到 1990 年代:
| 时间 | 方案 | 状态 |
|---|---|---|
| 1991 | Microsoft COM IDispatch | 商业方案,仅限 Windows |
| 1995 | MFC CRuntimeClass 宏 | 框架内方案 |
| 1998 | C++98 std::type_info | 标准化 RTTI,最小化 |
| 2003 | Boost.Serialization | 通过模板与宏实现 |
| 2005 | Boost.MPL / Fusion | 模板元编程库 |
| 2011 | C++11 type_traits | 标准化类型萃取 |
| 2014 | C++14 std::integer_sequence | 编译期整数序列 |
| 2017 | C++17 if constexpr、std::any、std::variant、std::optional | 静态多态 |
| 2018 | Boost.DLL、Boost.PFR | 自动 POD 反射 |
| 2020 | C++20 Concepts | 类型约束 |
| 2022 | Magic Enum、rttr | 第三方反射库成熟 |
| 2024 | C++26 P2996 R5 进入投票 | 标准化静态反射在望 |
C++26 反射提案(P2996)的诞生
C++ 反射的标准化提案最早可追溯到 2003 年(Daveed Vandevoorde 的 N2958),但直到 2014 年前后由 Andrew Sutton、Andrew Tomazos、Botond Ballo 等人重启,才逐步形成可行的方案。最终 P2996 提案由 Andrew Sutton 与 Daveed Vandevoorde 主导,于 2024 年进入 C++26 草案投票阶段。
P2996 的核心创新是引入反射运算符 ^^T 与反射类型 std::meta::info:
constexpr auto info = ^^std::vector<int>; // 获取类型的反射值
// info 是 consteval 计算的 std::meta::info 类型
std::meta::info 是一种类型擦除的反射值,可以表示类型、成员、函数、模板等各种程序结构。通过它可以在编译期枚举类的所有成员、获取字段类型与名称、生成代码。
P2996 的关键设计目标:
- 零运行时开销:所有反射操作在编译期完成,运行时无任何额外开销;
- 图灵完备的元编程:通过
consteval函数可以在编译期执行任意计算; - 可组合:反射值可作为函数参数与返回值,便于构建复杂元编程库;
- 与现有特性兼容:与模板、Concepts、
constexpr完美整合。
形式化定义
反射的定义
设 为程序的全体静态结构(类型、函数、变量、模板等), 为元数据空间。反射是映射:
使得程序员可以在某个执行阶段(编译期或运行期)访问 中的元信息。
依据反射发生的时间点,可分为:
- 静态反射:,无运行时开销;
- 动态反射:,需要运行期元数据存储。
元编程的定义
元编程是程序 操作程序 的过程:
其中 是元程序, 是目标程序, 是变换后的程序。在 C++ 中, 通常是模板与 consteval 函数, 是类型或值, 是生成的代码。
C++ 元编程分为三类:
- 类型元编程:操作类型,生成新类型。例:
std::remove_reference<T>::type。 - 值元编程:操作编译期值,生成新值。例:
constexpr斐波那契。 - 混合元编程:操作类型与值,生成代码。例:反射式序列化。
元编程的图灵完备性
定理:C++ 模板元编程是图灵完备的。
证明草图:可以模拟任意 Turing 机。构造如下:
- 状态:模板参数列表中的整数
int N表示状态 ; - 纸带:模板参数列表
std::integer_sequence<int, ...>; - 转移函数:模板特化匹配实现状态转移;
- 读写头:模板特化中的递归实现读写头移动。
由于可以模拟 Turing 机,C++ 模板可以计算任意可计算函数。代价是编译期可能任意长,编译器有递归深度限制(通常 1024,可调)。
反射不变式
设计反射系统时,必须维护以下不变式:
- 不变式 R1(一致性):反射元数据必须与源代码一致。若类有 N 个成员,反射必须返回 N 个成员。
- 不变式 R2(确定性):反射操作的输出由输入唯一决定,与编译时机、编译器版本无关(在标准化前提下)。
- 不变式 R3(零开销):静态反射不引入任何运行时开销。运行时反射的开销必须可量化、可避免。
- 不变式 R4(可组合性):反射操作可以组合使用,构建复杂元编程库。
元编程的代数结构
C++ 元编程类型集合 与类型变换集合 构成一个代数结构:
- 复合运算:,对应模板嵌套;
- 单位元:,对应
std::type_identity<T>; - 结合律:;
- 幂等性:(如
std::remove_const)。
形成幺半群(monoid)。这是函数式元编程(如 Boost.MPL、Boost.Hana)的代数基础。
理论推导
推导 1:编译期与运行期反射的开销对比
设类型 有 个成员。静态反射需要存储 个反射值(编译期),运行时反射需要存储 个元数据项(运行期)。
定理 1.1:静态反射的运行时开销为零,即:
证明:静态反射的所有计算在编译期完成,生成的代码与手写代码等价。
定理 1.2:运行时反射的开销为:
证明:元数据需存储 个成员的描述(名称、类型、偏移),共 字节。运行期查找通过哈希或二分,。
推导 2:模板递归的编译时间
设模板递归深度为 ,每次实例化耗时 ,则总编译时间为:
其中 是子模板的编译时间。对于线性递归,(每次实例化需扫描已实例化集合)。
定理 2.1:使用 constexpr 函数替代模板递归可降低编译时间。
证明:constexpr 函数的递归在编译期单次执行,无模板实例化开销。对于斐波那契 ,模板版本编译时间 (展开为表达式树),constexpr 版本编译时间 。
推导 3:SFINAE 与 Concepts 的表达能力
定理 3.1:Concepts 的表达能力严格强于 SFINAE。
证明:
- 任何 SFINAE 表达的约束都可由 Concepts 表达(使用
requires表达式); - Concepts 可以表达原子约束的合取与析取(
&&、||),SFINAE 只能表达合取(嵌套enable_if); - Concepts 可以触发重载解析的子排序(subsumption),SFINAE 不能。
故 Concepts 严格强于 SFINAE。
推导 4:反射式代码生成的代码膨胀
定理 4.1:使用静态反射生成的代码体积等于手写代码体积。
证明:静态反射在编译期生成等价代码,无额外抽象层。例如,反射式 to_json 生成的代码与手写 to_json 完全等价,无虚函数调用、无动态分发。
推论 4.2:静态反射是零开销抽象的一种形式。
基础概念
C++ 反射的四种实现策略
策略 1:RTTI(运行时类型信息)
#include <typeinfo>
#include <iostream>
class Base { public: virtual ~Base() = default; };
class Derived : public Base {};
void demo(Base* b) {
const std::type_info& ti = typeid(*b);
std::cout << "Type: " << ti.name() << "\n";
if (Derived* d = dynamic_cast<Derived*>(b)) {
std::cout << "Is Derived\n";
}
}
优点:标准库原生支持,无需额外代码。
缺点:仅提供类型名与向下转型,无法枚举成员;运行时开销(虚表查找、字符串比较);编译器可禁用(-fno-rtti)。
策略 2:宏注册反射
#include <boost/describe.hpp>
#include <string>
#include <iostream>
struct Person {
std::string name;
int age;
double height;
};
BOOST_DESCRIBE_STRUCT(Person, (), (name, age, height))
int main() {
Person p{"Alice", 30, 1.65};
// 使用 Boost.Describe 反射
boost::mp11::mp_for_each<boost::describe::describe_members<Person>>(
[&](auto D) {
std::cout << D.name << " = " << p.*D.pointer << "\n";
}
);
}
优点:零运行时开销;可移植(C++11);声明简洁。 缺点:需手动宏注册;无法处理继承;仅限编译期已知成员。
策略 3:模板元编程
#include <type_traits>
/// 编译期斐波那契
template <int N>
struct Fibonacci {
static constexpr int value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};
template <>
struct Fibonacci<0> { static constexpr int value = 0; };
template <>
struct Fibonacci<1> { static constexpr int value = 1; };
static_assert(Fibonacci<10>::value == 55);
优点:完全标准化;图灵完备;零开销。 缺点:语法笨重;编译时间长;错误信息晦涩。
策略 4:C++26 静态反射(P2996)
// C++26 草案(语法可能调整)
#include <meta>
#include <iostream>
struct Person {
std::string name;
int age;
};
template <typename T>
void describe() {
constexpr auto info = ^^T;
std::cout << "Type: " << name_of(info) << "\n";
std::cout << "Members:\n";
for (auto member : nonstatic_data_members_of(info)) {
std::cout << " " << name_of(member) << ": "
<< name_of(type_of(member)) << "\n";
}
}
int main() {
describe<Person>();
// 输出:
// Type: Person
// Members:
// name: std::string
// age: int
}
优点:标准化;零开销;图灵完备;无需宏注册。 缺点:尚在草案;编译器支持有限(Clang trunk 已部分实现)。
模板元编程基础构件
| 构件 | 用途 | 例子 |
|---|---|---|
std::integral_constant<T, v> | 包装静态常量 | std::true_type、std::false_type |
std::conditional<B, T, F> | 类型 if-else | std::conditional<sizeof(int)==4, int, long>::type |
std::enable_if<B, T> | SFINAE 启用 | template <typename T, typename = std::enable_if_t<...>> |
std::tuple<Ts...> | 类型列表 | std::tuple<int, double, std::string> |
std::integer_sequence<T, ...> | 整数序列 | std::make_index_sequence<N> |
std::type_identity<T> | 类型身份(避免推导) | template <typename T> void f(std::type_identity<T>) |
C++17 if constexpr:现代元编程的核心
template <typename T>
auto get_value(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t;
} else if constexpr (std::is_same_v<T, std::string>) {
return t.length();
} else {
return t;
}
}
if constexpr 在编译期求值条件,仅编译匹配分支,避免 SFINAE 的复杂语法。
Concepts:类型约束的现代化
#include <concepts>
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <Numeric T>
T sum(const std::vector<T>& vec) {
T total = 0;
for (auto x : vec) total += x;
return total;
}
Concepts 提供了清晰的类型约束语法,替代了 SFINAE 的笨拙写法。
代码示例
示例 1:编译期斐波那契(多种实现)
#include <iostream>
// 实现 1:模板特化(C++98 风格)
template <int N>
struct FibTMP {
static constexpr int value = FibTMP<N-1>::value + FibTMP<N-2>::value;
};
template <> struct FibTMP<0> { static constexpr int value = 0; };
template <> struct FibTMP<1> { static constexpr int value = 1; };
// 实现 2:constexpr 函数(C++14 风格)
constexpr int fibConstexpr(int n) {
if (n <= 1) return n;
return fibConstexpr(n-1) + fibConstexpr(n-2);
}
// 实现 3:consteval 函数(C++20 风格,强制编译期)
consteval int fibConsteval(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int c = a + b;
a = b; b = c;
}
return b;
}
int main() {
static_assert(FibTMP<10>::value == 55);
static_assert(fibConstexpr(10) == 55);
static_assert(fibConsteval(10) == 55);
return 0;
}
示例 2:类型列表操作
#include <type_traits>
#include <tuple>
/// 类型列表:使用 std::tuple
template <typename... Ts>
struct TypeList {
static constexpr size_t size = sizeof...(Ts);
};
/// 取第一个类型
template <typename List> struct Front;
template <typename Head, typename... Tail>
struct Front<TypeList<Head, Tail...>> {
using type = Head;
};
/// 移除第一个类型
template <typename List> struct PopFront;
template <typename Head, typename... Tail>
struct PopFront<TypeList<Head, Tail...>> {
using type = TypeList<Tail...>;
};
/// 在头部添加类型
template <typename T, typename List> struct PushFront;
template <typename T, typename... Ts>
struct PushFront<T, TypeList<Ts...>> {
using type = TypeList<T, Ts...>;
};
/// 过滤:保留满足谓词的类型
template <template <typename> class Pred, typename List>
struct Filter {
using type = TypeList<>;
};
template <template <typename> class Pred, typename Head, typename... Tail>
struct Filter<Pred, TypeList<Head, Tail...>> {
private:
using FilteredTail = typename Filter<Pred, TypeList<Tail...>>::type;
public:
using type = std::conditional_t<
Pred<Head>::value,
typename PushFront<Head, FilteredTail>::type,
FilteredTail
>;
};
// 使用:过滤出整数类型
template <typename T>
struct IsInt : std::is_same<T, int> {};
using TL = TypeList<int, double, int, char, int, float>;
using FilteredTL = Filter<IsInt, TL>::type; // TypeList<int, int, int>
static_assert(std::is_same_v<
FilteredTL,
TypeList<int, int, int>
>);
示例 3:使用 if constexpr 实现编译期分支
#include <type_traits>
#include <string>
#include <vector>
template <typename T>
std::string to_string(const T& value) {
if constexpr (std::is_arithmetic_v<T>) {
return std::to_string(value);
} else if constexpr (std::is_same_v<T, std::string>) {
return value;
} else if constexpr (std::is_same_v<T, char>) {
return std::string(1, value);
} else if constexpr (std::is_same_v<T, bool>) {
return value ? "true" : "false";
} else {
return "<unknown>";
}
}
示例 4:使用宏实现简单反射
#include <iostream>
#include <string>
#include <tuple>
// 反射宏:定义字段
#define REFLECT_FIELDS(...) \
auto fields() { return std::make_tuple(__VA_ARGS__); } \
auto field_names() { return std::make_tuple(#__VA_ARGS__); }
struct Person {
std::string name;
int age;
double height;
REFLECT_FIELDS(&name, &age, &height)
};
int main() {
Person p{"Alice", 30, 1.65};
auto fields = p.fields();
auto names = p.field_names();
std::cout << "Person has " << std::tuple_size<decltype(fields)>::value << " fields\n";
std::apply([&](auto... ptrs) {
((std::cout << p.*ptrs << "\n"), ...);
}, fields);
}
示例 5:使用 Boost.PFR 自动反射 POD
#include <boost/pfr.hpp>
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
double height;
};
int main() {
Person p{"Alice", 30, 1.65};
// 自动遍历 POD 字段
boost::pfr::for_each_field(p, [](const auto& field) {
std::cout << field << "\n";
});
// 通过名称获取(C++20 起的实验特性)
std::cout << "Name: " << boost::pfr::get<0>(p) << "\n";
std::cout << "Age: " << boost::pfr::get<1>(p) << "\n";
}
示例 6:序列化库(基于反射)
#include <iostream>
#include <string>
#include <sstream>
#include <tuple>
/// 通用序列化:遍历字段输出为 JSON
template <typename T>
std::string serialize(const T& obj) {
std::ostringstream oss;
oss << "{";
bool first = true;
boost::pfr::for_each_field(obj, [&](const auto& field, [[maybe_unused]] auto idx) {
if (!first) oss << ", ";
first = false;
oss << "\"" << boost::pfr::get_name<idx, T>() << "\": ";
oss << "\"" << field << "\""; // 简化:所有字段加引号
});
oss << "}";
return oss.str();
}
struct Product {
std::string name;
double price;
int stock;
};
int main() {
Product p{"Widget", 9.99, 100};
std::cout << serialize(p) << "\n";
// 输出:{"name": "Widget", "price": "9.99", "stock": "100"}
}
示例 7:依赖注入容器(基于类型注册)
#include <any>
#include <functional>
#include <memory>
#include <unordered_map>
#include <typeindex>
class Container {
public:
template <typename Interface, typename Impl>
void register_type() {
registry_[std::type_index(typeid(Interface))] = []() {
return std::any(std::make_shared<Impl>());
};
}
template <typename Interface>
std::shared_ptr<Interface> resolve() {
auto it = registry_.find(std::type_index(typeid(Interface)));
if (it == registry_.end()) return nullptr;
return std::any_cast<std::shared_ptr<Interface>>(it->second());
}
private:
std::unordered_map<std::type_index, std::function<std::any()>> registry_;
};
// 使用:
struct ILogger { virtual void log(const std::string&) = 0; virtual ~ILogger() = default; };
class ConsoleLogger : public ILogger {
public:
void log(const std::string& msg) override {
std::cout << "[LOG] " << msg << "\n";
}
};
int main() {
Container c;
c.register_type<ILogger, ConsoleLogger>();
auto logger = c.resolve<ILogger>();
logger->log("Hello, DI!");
}
示例 8:使用 SFINAE 实现类型约束
#include <type_traits>
// SFINAE 风格:基于 std::enable_if
template <typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T add_sfinae(T a, T b) { return a + b; }
// Concepts 风格(C++20)
template <typename T>
requires std::integral<T>
T add_concept(T a, T b) { return a + b; }
// 简化语法
template <std::integral T>
T add_simple(T a, T b) { return a + b; }
// 使用 requires 表达式
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
template <Addable T>
T add_custom(T a, T b) { return a + b; }
示例 9:使用 C++26 反射(实验性)
// C++26 草案(语法可能调整)
#include <meta>
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
double height;
};
template <typename T>
void print_struct() {
constexpr auto info = ^^T;
std::cout << "struct " << name_of(info) << " {\n";
for (auto member : nonstatic_data_members_of(info)) {
std::cout << " " << name_of(type_of(member))
<< " " << name_of(member) << ";\n";
}
std::cout << "};\n";
}
// 反射式序列化
template <typename T>
std::string to_json(const T& obj) {
std::string result = "{";
bool first = true;
constexpr auto members = nonstatic_data_members_of(^^T);
template for (constexpr auto member : members) {
if (!first) result += ", ";
first = false;
result += "\"" + name_of(member) + "\": ";
result += to_string(obj.[member]); // 反射式访问
}
result += "}";
return result;
}
int main() {
print_struct<Person>();
// 输出:
// struct Person {
// std::string name;
// int age;
// double height;
// };
Person p{"Alice", 30, 1.65};
std::cout << to_json(p) << "\n";
}
对比分析
反射方案横向对比
| 方案 | 实现机制 | 运行时开销 | 编译时开销 | 适用场景 |
|---|---|---|---|---|
| RTTI | typeid/dynamic_cast | 中 | 低 | 向下转型、类型识别 |
| 宏注册 | 手动宏 | 零 | 中 | 序列化、ORM |
| 模板元编程 | SFINAE/if constexpr | 零 | 高 | 类型计算、约束 |
| Boost.PFR | 聚合体反射 | 零 | 中 | POD 序列化 |
| Boost.Describe | 宏注册 + TMP | 零 | 中 | 任意类反射 |
| Magic Enum | 模板技巧 | 零 | 中 | 枚举反射 |
| rttr | 运行时库 | 高 | 低 | 动态语言集成 |
| C++26 P2996 | 语言级静态反射 | 零 | 中(待优化) | 标准化方案 |
Concepts vs SFINAE
| 维度 | SFINAE | Concepts |
|---|---|---|
| 语法复杂度 | 高 | 低 |
| 错误信息 | 难懂 | 清晰 |
| 表达能力 | 中 | 高(支持析取) |
| 重载解析 | 不支持子排序 | 支持子排序 |
| 标准化 | C++11 | C++20 |
| 编译速度 | 慢 | 快 |
元编程技术对比
| 技术 | 时代 | 典型用法 | 编译时间 |
|---|---|---|---|
| 模板特化 | C++98 | 阶乘、斐波那契 | 慢 |
| SFINAE | C++11 | enable_if | 慢 |
constexpr 函数 | C++14 | 编译期计算 | 快 |
if constexpr | C++17 | 编译期分支 | 快 |
| Concepts | C++20 | 类型约束 | 快 |
| 反射(C++26) | 草案 | 反射式代码生成 | 中 |
常见陷阱
陷阱 1:模板递归过深导致编译失败
template <int N>
struct Fib { static constexpr int value = Fib<N-1>::value + Fib<N-2>::value; };
// Fib<1000> 会触发编译器递归深度限制(通常 1024)
修复:使用 constexpr 函数或尾递归优化。
陷阱 2:SFINAE 失败导致重载不可达
template <typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
void f(T) {}
// 调用 f(3.14) 时,编译器找不到匹配,报错信息晦涩
修复:使用 Concepts,错误信息更清晰。
陷阱 3:dynamic_cast 的运行时开销
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b); // O(深度) RTTI 查找
dynamic_cast 在深度继承链下开销显著,约为 50–200ns。性能敏感代码应避免。
陷阱 4:宏注册遗漏字段
struct Person {
std::string name;
int age;
// 添加新字段 height 后忘记更新宏
};
BOOST_DESCRIBE_STRUCT(Person, (), (name, age))
// height 不会出现在反射中
修复:使用 Boost.PFR 自动反射 POD,或使用 C++26 反射。
陷阱 5:typeid 名称不可移植
#include <typeinfo>
#include <iostream>
std::cout << typeid(int).name(); // 不同编译器输出不同
// GCC: i
// MSVC: int
// Clang: i
typeid().name() 的返回值由实现定义,不能用于跨平台序列化。修复:使用 std::type_index 与自定义名称映射。
陷阱 6:反射与 ABI 兼容性
宏注册反射依赖字段顺序,若类布局在不同编译单元或不同 ABI 下不一致,反射将产生错误结果。修复:使用标准化的静态反射(C++26)。
陷阱 7:模板实例化爆炸
template <typename T>
void process() { /* ... */ }
// 显式实例化所有组合
process<int>();
process<double>();
process<std::string>();
process<std::vector<int>>();
// ...
模板实例化过多会导致编译时间爆炸与二进制体积膨胀。修复:使用 if constexpr 减少实例化,或使用外部模板(extern template)。
陷阱 8:consteval 不能在运行时调用
consteval int square(int x) { return x * x; }
int main() {
int n;
std::cin >> n;
// square(n); // 错误:n 不是常量表达式
return 0;
}
consteval 函数只能在编译期调用。需要运行期计算请用 constexpr。
工程实践
实践 1:选择合适的反射方案
- 需要运行时反射(如脚本绑定、动态加载):使用
rttr或自建运行时反射库。 - 仅需要编译期反射(如序列化、ORM 代码生成):使用 Boost.PFR 或 Boost.Describe。
- 枚举反射:使用 Magic Enum 或自建模板技巧。
- C++26 及以后:优先使用标准静态反射。
实践 2:使用 if constexpr 替代 SFINAE
// 旧式 SFINAE
template <typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
T abs_value(T x) { return x < 0 ? -x : x; }
template <typename T,
std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
T abs_value(T x) { return std::fabs(x); }
// 新式 if constexpr(C++17)
template <typename T>
T abs_value(T x) {
if constexpr (std::is_integral_v<T>) {
return x < 0 ? -x : x;
} else if constexpr (std::is_floating_point_v<T>) {
return std::fabs(x);
}
}
实践 3:使用 Concepts 改善错误信息
template <typename T>
concept Serializable = requires(const T& t) {
{ to_string(t) } -> std::convertible_to<std::string>;
};
template <Serializable T>
void save(const T& obj) {
std::cout << to_string(obj);
}
实践 4:编译期常量传播
// 旧式:宏
#define BUFFER_SIZE 1024
// 新式:constexpr
constexpr size_t buffer_size = 1024;
static_assert(buffer_size > 0);
// 模板参数
template <size_t N>
class Buffer { /* ... */ };
Buffer<buffer_size> buf;
实践 5:使用 std::tuple 实现异构容器
#include <tuple>
#include <iostream>
struct Data {
int id;
std::string name;
double value;
};
int main() {
Data d{1, "Alice", 3.14};
// 转为 tuple
auto t = std::make_tuple(d.id, d.name, d.value);
// 遍历
std::apply([](auto&&... args) {
((std::cout << args << "\n"), ...);
}, t);
}
实践 6:使用 std::variant 替代虚函数
#include <variant>
#include <iostream>
#include <vector>
struct Circle { double radius; };
struct Square { double side; };
struct Triangle { double a, b, c; };
using Shape = std::variant<Circle, Square, Triangle>;
double area(const Shape& s) {
return std::visit([](const auto& shape) -> double {
using T = std::decay_t<decltype(shape)>;
if constexpr (std::is_same_v<T, Circle>) {
return 3.14159 * shape.radius * shape.radius;
} else if constexpr (std::is_same_v<T, Square>) {
return shape.side * shape.side;
} else if constexpr (std::is_same_v<T, Triangle>) {
double s = (shape.a + shape.b + shape.c) / 2;
return std::sqrt(s * (s-shape.a) * (s-shape.b) * (s-shape.c));
}
}, s);
}
int main() {
std::vector<Shape> shapes = {
Circle{1.0}, Square{2.0}, Triangle{3, 4, 5}
};
for (const auto& s : shapes) {
std::cout << area(s) << "\n";
}
}
std::variant + std::visit 是零开销的多态替代,避免了虚函数的间接调用。
案例研究
案例 1:基于反射的 ORM(对象关系映射)
#include <boost/describe.hpp>
#include <iostream>
#include <sstream>
#include <string>
struct User {
int id;
std::string name;
std::string email;
int age;
};
BOOST_DESCRIBE_STRUCT(User, (), (id, name, email, age))
/// 生成 INSERT SQL
template <typename T>
std::string generate_insert_sql(const T& obj, const std::string& table) {
std::ostringstream oss;
oss << "INSERT INTO " << table << " (";
bool first = true;
boost::mp11::mp_for_each<boost::describe::describe_members<T>>(
[&](auto D) {
if (!first) oss << ", ";
first = false;
oss << D.name;
}
);
oss << ") VALUES (";
first = true;
boost::mp11::mp_for_each<boost::describe::describe_members<T>>(
[&](auto D) {
if (!first) oss << ", ";
first = false;
oss << "'" << obj.*D.pointer << "'";
}
);
oss << ");";
return oss.str();
}
int main() {
User u{1, "Alice", "alice@example.com", 30};
std::cout << generate_insert_sql(u, "users") << "\n";
// 输出:INSERT INTO users (id, name, email, age) VALUES ('1', 'Alice', 'alice@example.com', '30');
}
案例 2:JSON 序列化框架
#include <boost/describe.hpp>
#include <boost/pfr.hpp>
#include <string>
#include <sstream>
/// 通用 JSON 序列化
template <typename T>
std::string to_json(const T& obj) {
std::ostringstream oss;
oss << "{";
bool first = true;
boost::mp11::mp_for_each<boost::describe::describe_members<T>>(
[&](auto D) {
if (!first) oss << ", ";
first = false;
oss << "\"" << D.name << "\": ";
const auto& value = obj.*D.pointer;
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, std::string>) {
oss << "\"" << value << "\"";
} else if constexpr (std::is_arithmetic_v<std::decay_t<decltype(value)>>) {
oss << value;
} else {
oss << "null"; // 简化处理
}
}
);
oss << "}";
return oss.str();
}
案例 3:依赖注入容器(完整实现)
#include <any>
#include <functional>
#include <memory>
#include <unordered_map>
#include <typeindex>
#include <stdexcept>
class DIContainer {
public:
template <typename Interface, typename Impl, typename... Deps>
void register_type() {
registry_[std::type_index(typeid(Interface))] = [this]() {
return std::any(std::make_shared<Impl>(resolve<Deps>()...));
};
}
template <typename Interface>
void register_instance(std::shared_ptr<Interface> instance) {
registry_[std::type_index(typeid(Interface))] =
[instance]() { return std::any(instance); };
}
template <typename Interface>
std::shared_ptr<Interface> resolve() {
auto it = registry_.find(std::type_index(typeid(Interface)));
if (it == registry_.end()) {
throw std::runtime_error("Type not registered");
}
return std::any_cast<std::shared_ptr<Interface>>(it->second());
}
private:
std::unordered_map<std::type_index, std::function<std::any()>> registry_;
};
// 使用:
struct ILogger { virtual void log(const std::string&) = 0; virtual ~ILogger() = default; };
struct IDatabase { virtual void query(const std::string&) = 0; virtual ~IDatabase() = default; };
class ConsoleLogger : public ILogger {
public:
void log(const std::string& msg) override {
std::cout << "[LOG] " << msg << "\n";
}
};
class SqlDatabase : public IDatabase {
public:
SqlDatabase(std::shared_ptr<ILogger> logger) : logger_(logger) {}
void query(const std::string& sql) override {
logger_->log("Executing: " + sql);
// ... 执行查询 ...
}
private:
std::shared_ptr<ILogger> logger_;
};
int main() {
DIContainer container;
container.register_type<ILogger, ConsoleLogger>();
container.register_type<IDatabase, SqlDatabase, ILogger>();
auto db = container.resolve<IDatabase>();
db->query("SELECT * FROM users");
}
案例 4:枚举反射(Magic Enum 风格)
#include <array>
#include <string_view>
#include <algorithm>
namespace magic_enum {
/// 自实现简化版:通过模板技巧获取枚举值与名称
template <typename E>
constexpr auto enum_values() {
// 简化实现:假设枚举从 0 开始连续
constexpr size_t max = 64;
std::array<E, max> values{};
for (size_t i = 0; i < max; ++i) {
values[i] = static_cast<E>(i);
}
return values;
}
template <typename E>
constexpr std::string_view enum_name(E value) {
// 简化实现:通过 __PRETTY_FUNCTION__ 提取名称
// 实际 magic_enum 库使用更复杂的技巧
return "<enum value>";
}
}
enum class Color { Red, Green, Blue };
int main() {
using namespace magic_enum;
auto values = enum_values<Color>();
for (size_t i = 0; i < 3; ++i) {
std::cout << static_cast<int>(values[i]) << ": "
<< enum_name(values[i]) << "\n";
}
}
实际生产中建议直接使用 Magic Enum 库。
案例 5:基于反射的状态机
#include <variant>
#include <iostream>
#include <string>
/// 状态:使用变体表示
struct Idle {};
struct Running {};
struct Paused {};
struct Stopped {};
using State = std::variant<Idle, Running, Paused, Stopped>;
/// 事件
struct StartEvent {};
struct PauseEvent {};
struct ResumeEvent {};
struct StopEvent {};
/// 状态机
class StateMachine {
public:
StateMachine() : state_(Idle{}) {}
void handle(const StartEvent&) {
std::visit([](auto& s) {
using T = std::decay_t<decltype(s)>;
if constexpr (std::is_same_v<T, Idle>) {
std::cout << "Idle -> Running\n";
s = Running{};
}
}, state_);
}
void handle(const PauseEvent&) {
std::visit([](auto& s) {
using T = std::decay_t<decltype(s)>;
if constexpr (std::is_same_v<T, Running>) {
std::cout << "Running -> Paused\n";
s = Paused{};
}
}, state_);
}
void handle(const StopEvent&) {
std::visit([](auto& s) {
using T = std::decay_t<decltype(s)>;
if constexpr (!std::is_same_v<T, Stopped>) {
std::cout << "-> Stopped\n";
s = Stopped{};
}
}, state_);
}
private:
State state_;
};
int main() {
StateMachine sm;
sm.handle(StartEvent{});
sm.handle(PauseEvent{});
sm.handle(StopEvent{});
}
案例 6:基于反射的命令模式
#include <functional>
#include <string>
#include <unordered_map>
#include <iostream>
class CommandRegistry {
public:
using Handler = std::function<void(const std::string&)>;
void register_command(const std::string& name, Handler handler) {
handlers_[name] = std::move(handler);
}
void execute(const std::string& name, const std::string& args) {
auto it = handlers_.find(name);
if (it != handlers_.end()) {
it->second(args);
} else {
std::cout << "Unknown command: " << name << "\n";
}
}
private:
std::unordered_map<std::string, Handler> handlers_;
};
int main() {
CommandRegistry registry;
registry.register_command("greet", [](const std::string& name) {
std::cout << "Hello, " << name << "!\n";
});
registry.register_command("add", [](const std::string& args) {
int a, b;
std::sscanf(args.c_str(), "%d %d", &a, &b);
std::cout << a << " + " << b << " = " << (a + b) << "\n";
});
registry.execute("greet", "World");
registry.execute("add", "3 4");
}
性能分析
编译时间对比
| 技术 | 编译时间(1M 行代码) |
|---|---|
| 无元编程 | 30s |
| SFINAE 重度使用 | 5min |
if constexpr 替代 | 2min |
| Concepts 简化 | 1min |
| C++26 反射 | 1.5min(预计) |
运行时开销对比
| 方案 | 单次访问开销 |
|---|---|
| 直接成员访问 | 0 ns |
| 模板元编程生成代码 | 0 ns |
| Boost.PFR | 0 ns |
| Boost.Describe | 0 ns |
RTTI typeid | 5 ns |
dynamic_cast | 50–200 ns |
| rttr 反射调用 | 100–500 ns |
与现代 C++ 特性的整合
consteval(C++20)
consteval auto get_type_name() {
return "Person";
}
std::source_location(C++20)
#include <source_location>
void log(const std::string& msg,
const std::source_location& loc = std::source_location::current()) {
std::cout << "[" << loc.file_name() << ":" << loc.line() << "] " << msg << "\n";
}
std::expected(C++23)
template <typename T>
std::expected<std::string, std::error_code> get_field_name(int idx) {
// 反射式获取字段名
}
与 C++26 反射提案的协同
反射式序列化(C++26)
// C++26 草案
template <typename T>
std::string to_json(const T& obj) {
std::string result = "{";
bool first = true;
constexpr auto members = nonstatic_data_members_of(^^T);
template for (constexpr auto m : members) {
if (!first) result += ", ";
first = false;
result += "\"" + name_of(m) + "\": ";
// 反射式类型判断
if constexpr (type_of(m) == ^^std::string) {
result += "\"" + obj.[m] + "\"";
} else {
result += std::to_string(obj.[m]);
}
}
result += "}";
return result;
}
反射式依赖注入(C++26)
// C++26 草案:通过反射自动解析构造函数依赖
template <typename T>
std::shared_ptr<T> resolve(Container& c) {
constexpr auto ctors = constructors_of(^^T);
constexpr auto first_ctor = front_of(ctors);
constexpr auto params = parameters_of(first_ctor);
// 反射式调用构造函数
return std::make_shared<T>(resolve<type_of(params[0])>(c), ...);
}
反射式代码生成(C++26)
// C++26 草案:为任意类型生成 getter/setter
template <typename T>
struct Reflectable {
constexpr auto get_members() {
return nonstatic_data_members_of(^^T);
}
template for (constexpr auto m : get_members()) {
[[nodiscard]] auto& get_##m() { return [m]; }
void set_##m(const auto& v) { [m] = v; }
}
};
陷阱与限制
限制 1:编译器支持有限
C++26 反射提案 P2996 截至 2024 年仅在 Clang trunk 中部分实现,GCC 与 MSVC 尚未支持。生产代码需使用第三方库。
限制 2:模板递归深度限制
template <int N>
struct Fib { static constexpr int value = Fib<N-1>::value + Fib<N-2>::value; };
// Fib<1024> 触发 GCC 递归深度限制
编译器通常限制模板递归深度为 1024,可通过 -ftemplate-depth=N 调整。
限制 3:宏反射的语法污染
struct Person {
std::string name;
int age;
REFLECT(name, age) // 宏侵入式声明
};
宏反射需要侵入式声明,污染类定义。C++26 反射将消除此问题。
限制 4:RTTI 不可用
某些项目(如游戏引擎)禁用 RTTI 以减小体积与开销,dynamic_cast 与 typeid 不可用。修复:使用 Concepts 或模板技巧。
工程实践
实践 1:分层反射策略
- 底层:使用 Boost.PFR 或 Boost.Describe 提供基础反射能力;
- 中层:构建领域特定的序列化、ORM、RPC 库;
- 上层:业务代码使用高层抽象。
实践 2:减少模板实例化
// 错误:每种 T 实例化一个版本
template <typename T>
void process(T x) { /* ... */ }
// 正确:使用 if constexpr 减少
template <typename T>
void process(T x) {
if constexpr (std::is_integral_v<T>) {
process_integral(x);
} else {
process_other(x);
}
}
实践 3:使用 extern template 减少编译时间
// header.h
template <typename T>
void big_function(T);
// source.cpp
template <typename T>
void big_function(T) { /* 实现 */ }
template void big_function<int>(int);
template void big_function<double>(double);
// 其他 cpp
extern template void big_function<int>(int);
extern template void big_function<double>(double);
实践 4:概念约束的复合
template <typename T>
concept Serializable = requires(const T& t) {
{ t.serialize() } -> std::convertible_to<std::string>;
};
template <typename T>
concept Deserializable = requires(T& t, const std::string& s) {
{ t.deserialize(s) };
};
template <typename T>
concept BidirectionalSerializable = Serializable<T> && Deserializable<T>;
实践 5:使用 std::variant 替代类型擦除
// 旧式:void* + 类型标签
struct Event {
enum Type { Click, Key, Mouse } type;
void* data;
};
// 新式:variant
using Event = std::variant<ClickEvent, KeyEvent, MouseEvent>;
void handle(const Event& e) {
std::visit([](const auto& ev) {
using T = std::decay_t<decltype(ev)>;
if constexpr (std::is_same_v<T, ClickEvent>) {
// 处理点击
} else if constexpr (std::is_same_v<T, KeyEvent>) {
// 处理键盘
}
}, e);
}
案例研究:完整反射式 ORM
完整实现
#include <boost/describe.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <memory>
/// 字段元信息
struct FieldInfo {
std::string name;
std::string type;
bool is_primary_key = false;
bool nullable = false;
};
/// 表元信息
struct TableInfo {
std::string name;
std::vector<FieldInfo> fields;
};
/// 反射式 ORM 基础
template <typename T>
class ORM {
public:
static std::string create_table_sql() {
std::ostringstream oss;
oss << "CREATE TABLE " << get_table_name() << " (\n";
bool first = true;
boost::mp11::mp_for_each<boost::describe::describe_members<T>>(
[&](auto D) {
if (!first) oss << ",\n";
first = false;
oss << " " << D.name << " "
<< sql_type<typename std::remove_member_pointer<decltype(D.pointer)>::type>();
}
);
oss << "\n);";
return oss.str();
}
static std::string insert_sql(const T& obj) {
std::ostringstream oss;
oss << "INSERT INTO " << get_table_name() << " (";
bool first = true;
boost::mp11::mp_for_each<boost::describe::describe_members<T>>(
[&](auto D) {
if (!first) oss << ", ";
first = false;
oss << D.name;
}
);
oss << ") VALUES (";
first = true;
boost::mp11::mp_for_each<boost::describe::describe_members<T>>(
[&](auto D) {
if (!first) oss << ", ";
first = false;
oss << "'" << obj.*D.pointer << "'";
}
);
oss << ");";
return oss.str();
}
private:
static std::string get_table_name() {
return "t_" + std::string(typeid(T).name()); // 简化
}
template <typename U>
static std::string sql_type() {
if constexpr (std::is_same_v<U, int>) return "INTEGER";
else if constexpr (std::is_same_v<U, std::string>) return "TEXT";
else if constexpr (std::is_same_v<U, double>) return "REAL";
else return "BLOB";
}
};
struct User {
int id;
std::string name;
std::string email;
int age;
};
BOOST_DESCRIBE_STRUCT(User, (), (id, name, email, age))
int main() {
std::cout << ORM<User>::create_table_sql() << "\n\n";
User u{1, "Alice", "alice@example.com", 30};
std::cout << ORM<User>::insert_sql(u) << "\n";
}
基础题
Q1:使用模板元编程实现编译期阶乘 Factorial<N>。
Q2:使用 if constexpr 实现一个通用的 to_string 函数,支持 int、double、std::string、bool。
Q3:解释 dynamic_cast 与 std::variant 在多态实现上的差异。
Q4:使用 std::variant 与 std::visit 实现一个简单的状态机。
Q5:使用 Boost.PFR 反射一个 POD 结构体,并打印所有字段。
进阶题
Q6:实现一个类型列表 TypeList<Ts...>,提供 push_front、pop_front、at<I>、size 操作。
Q7:使用 SFINAE 与 Concepts 分别实现”仅整数类型可调用”的函数 f(T),对比两者语法。
Q8:使用宏注册反射实现一个 JSON 序列化框架,支持嵌套对象与数组。
Q9:分析以下代码的编译错误,并使用 Concepts 改善错误信息:
template <typename T>
T sum(T a, T b) { return a + b; }
sum(std::string("a"), std::string("b")); // 编译错误?
Q10:使用 std::variant 与 if constexpr 实现一个表达式求值器,支持加减乘除与变量引用。
挑战题
Q11:设计并实现一个完整的反射式序列化库,要求:
- 支持任意 POD 类型(通过 Boost.PFR);
- 支持嵌套对象与数组;
- 支持 JSON 与二进制两种格式;
- 提供 Concepts 约束。
Q12:使用 C++26 反射提案(P2996,假设编译器支持)实现一个自动生成的 to_string 函数,遍历任意结构体的所有成员。
Q13:实现一个依赖注入容器,要求:
- 支持构造函数注入;
- 自动解析依赖关系(通过反射);
- 支持单例与瞬时生命周期。
Q14:分析 C++26 反射对现有 ORM 框架(如 SQLAlchemy C++、ODB)的影响,给出迁移策略。
Q15:设计一个编译期类型计算库,要求:
- 支持类型列表操作(map、filter、fold);
- 提供与 Boost.MPL 等价的接口;
- 编译时间不超过 Boost.MPL 的 50%。
类型信息
基本写法:typeid
typeid(<类型或表达式>)
#include <typeinfo>
// 运行时类型信息
int x = 42;
const std::type_info& ti = typeid(x);
std::cout << ti.name(); // 类型名(编译器相关)
if (typeid(x) == typeid(int)) { /* 类型匹配 */ }
基本写法:dynamic_cast
dynamic_cast<<派生>*>(<基类*>)
// 运行时类型转换
Base* p = getBase();
if (Derived* d = dynamic_cast<Derived*>(p)) {
d->specificMethod();
}
编译期类型操作
基本写法:type_traits 类型特征
std::is_<特征><<类型>>::value
#include <type_traits>
// 类型判断
static_assert(std::is_integral_v<int>); // 是否整数
static_assert(std::is_pointer_v<int*>); // 是否指针
static_assert(std::is_class_v<std::string>); // 是否类
static_assert(std::is_base_of_v<Base, Derived>); // 继承关系
static_assert(std::is_convertible_v<int, double>); // 可转换
基本写法:类型变换
std::remove_const<...>::type 等
// 类型变换(C++14 起有 _t 别名)
using T1 = std::remove_const_t<const int>; // int
using T2 = std::remove_pointer_t<int*>; // int
using T3 = std::add_const_t<int>; // const int
using T4 = std::decay_t<const int&>; // int
using T5 = std::conditional_t<sizeof(int)==4, int, long>; // int
基本写法:decay 与 common_type
std::decay_t<T> std::common_type_t<T...>
// 类型退化(值传递语义)
template <typename T>
void func(T x) {
using D = std::decay_t<T>; // 去除引用、cv、数组退化
}
// 公共类型
using C = std::common_type_t<int, double>; // double
using C2 = std::common_type_t<char, short, int>; // int
SFINAE
基本写法:enable_if
std::enable_if_t<<条件>, <类型>>
#include <type_traits>
// SFINAE 条件启用
template <typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T addOne(T x) { return x + 1; }
addOne(42); // OK
// addOne(3.14); // 错误:float 非整数
基本写法:void_t 技巧
std::void_t<<表达式>...>
// 检测类型是否有某成员
template <typename T, typename = void>
struct has_size : std::false_type {};
template <typename T>
struct has_size<T, std::void_t<decltype(std::declval<T>().size())>>
: std::true_type {};
static_assert(has_size<std::vector<int>>::value); // true
static_assert(!has_size<int>::value); // true
编译期反射技巧
基本写法:聚合体反射
struct <数据> { ... }; + 模板
// 利用结构化绑定做字段遍历(技巧)
struct Point { int x; int y; };
template <typename T>
void printFields(const T& obj) {
// C++17 结构化绑定
const auto& [a, b] = obj;
std::cout << a << "," << b;
}
Point p{3, 4};
printFields(p);
基本写法:magic_get / Boost.PFR
boost::pfr::for_each_field
#include <boost/pfr.hpp>
// 无宏反射聚合体
struct Person { std::string name; int age; };
Person p{"Alice", 30};
boost::pfr::for_each_field(p, [](const auto& field) {
std::cout << field << " ";
});
// 输出 Alice 30
编译期字符串
基本写法:consteval 字符串处理
consteval <返回> <函数>()
// 编译期字符串操作
consteval size_t strLen(const char* s) {
size_t n = 0;
while (s[n]) ++n;
return n;
}
constexpr size_t len = strLen("hello"); // 5,编译期
元编程工具
基本写法:integral_constant
std::integral_constant<<类型>, <值>>
// 编译期常量类型
using Two = std::integral_constant<int, 2>;
static_assert(Two::value == 2);
// bool 特化
using True = std::true_type;
using False = std::false_type;
static_assert(True::value);
基本写法:编译期循环展开
template <size_t... I>
// 编译期整数序列
template <size_t... I>
void printIndices(std::index_sequence<I...>) {
((std::cout << I << " "), ...); // 折叠表达式
}
printIndices(std::make_index_sequence<5>{});
// 输出 0 1 2 3 4