C++元编程:可变模板参数包、异构容器与反射机制深度解析
立即解锁
发布时间: 2025-08-20 01:47:49 阅读量: 1 订阅数: 3 


C++高性能编程:从入门到精通
### C++ 元编程:可变模板参数包、异构容器与反射机制深度解析
#### 1. 可变模板参数包
可变模板参数包允许程序员创建能接受任意数量参数的模板函数。
##### 1.1 可变数量参数函数示例
若不使用可变模板参数包来创建将任意数量参数转换为字符串的函数,需为每个参数数量单独创建函数:
```cpp
// 单个参数转换为字符串
template <typename T0>
auto make_string(const T0& v0) -> std::string {
auto sstr = std::ostringstream{};
sstr << v0;
return sstr.str();
}
// 两个参数转换为字符串
template <typename T0, typename T1>
auto make_string(const T0& v0, const T1& v1) -> std::string {
return make_string(v0) + " " + make_string(v1);
}
// 三个参数转换为字符串
template <typename T0, typename T1, typename T2>
auto make_string(const T0& v0, const T1& v1, const T2& v2) -> std::string {
return make_string(v0, v1) + " " + make_string(v2);
}
```
使用示例:
```cpp
auto str0 = make_string(42);
auto str1 = make_string(42, "hi");
auto str2 = make_string(42, "hi", true);
```
若需要处理大量参数,这种方式会很繁琐。而使用参数包,可实现一个接受任意数量参数的函数。
##### 1.2 构建可变参数包
参数包通过在类型名前加三个点来标识,可变参数后的三个点用于展开参数包,中间用逗号分隔。
以下是 `expand_pack` 函数示例:
```cpp
template <typename ...Ts>
auto expand_pack(const Ts& ...values) {
auto tuple = std::tie(values...);
}
```
调用示例:
```cpp
expand_pack(42, std::string{"hi"});
```
编译器会生成类似如下的函数:
```cpp
auto expand_pack(const int& v0, const std::string& v1) {
auto tuple = std::tie(v0, v1);
}
```
参数包各部分展开情况如下表:
| 表达式 | 展开结果 |
| --- | --- |
| `template <typename... Ts>` | `template <typename T0, typename T1>` |
| `expand_pack(const Ts& ...values)` | `expand_pack(const T0& v0, const T1& v1)` |
| `std::tie(values...)` | `std::tie(v0, v1)` |
##### 1.3 使用可变参数包创建 `make_string` 函数
为了将每个参数转换为字符串,需要遍历参数包。由于无法直接遍历参数包,可将其转换为元组,再使用 `tuple_for_each` 函数进行遍历:
```cpp
template <typename ...Ts>
auto make_string(const Ts& ...values) {
auto sstr = std::ostringstream{};
// 创建可变参数包的元组
auto tuple = std::tie(values...);
// 遍历元组
tuple_for_each(tuple, [&sstr](const auto& v){ sstr << v; });
return sstr.str();
}
```
#### 2. 动态大小的异构容器
`std::tuple` 是一个固定大小和元素位置的异构容器,类似于普通结构体,但没有命名成员变量。如何创建一个可变大小(如 `std::vector`、`std::list` 等)且能存储不同类型的容器呢?由于容器大小在运行时改变,不能使用编译时编程生成代码。
##### 2.1 使用 `std::any` 作为动态大小异构容器的基础
`std::any` 是 C++17 新增的,若编译器不支持,可使用 Boost 库中的 `boost::any`。
示例:
```cpp
auto container = std::vector<std::any>{42, "hi", true};
```
但它有缺点,每次访问其中的值时,都需要在运行时进行类型检查,编译时会完全丢失存储值的类型信息,需要依赖运行时类型检查。
遍历示例:
```cpp
for(const auto& a: container) {
if(a.type() == typeid(int)) {
const auto& value = std::any_cast<int>(a);
std::cout << value;
}
else if(a.type() == typeid(const char*)) {
const auto& value = std::any_cast<const char*>(a);
std::cout << value;
}
else if(a.type() == typeid(bool)) {
const auto& value = std::any_cast<bool>(a);
std::cout << value;
}
}
```
以下代码无法编译,因为编译器不知道 `std::any` 中存储的内容:
```cpp
for(const auto& a: container) {
std::cout << a; // 无法编译
}
```
##### 2.2 `std::variant`
若能放弃在容器中存储任意类型的能力,而专注于容器初始化时声明的一组固定类型,`std::variant` 是更好的选择。
`std::variant` 相对于 `std::any` 有两个主要优点:
- 不将包含的类型存储在堆上。
- 可以使用多态 lambda 调用,无需明确知道当前包含的类型。
示例:
```cpp
using VariantType = std::variant<int, std::string, bool>;
auto v = VariantType{}; // 变体为空
v = 7; // v 持有一个 int
v = std::string{"Bjarne"}; // v 持有一个 std::string,整数被覆盖
v = false; // v 持有一个 bool,std::string 被覆盖
```
##### 2.3 访问变体
访问 `std::variant` 中的变量时,使用全局函数 `std::visit()` 和多态 lambda:
```cpp
std::visit(
[](const auto& v){ std::cout << v; },
my_variant
);
```
编译器会为变体中包含的每个类型生成一个常规的
0
0
复制全文