C++模板元模板实战书籍讲解第一章(模板型模板参数与容器模板)

简介: C++模板元模板实战书籍讲解第一章(模板型模板参数与容器模板)

前言

一个深度学习框架的初步实现为例,讨论如何在一个相对较大的项目中深入应用元编程,为系统优化提供更多的可能。


以下是本书的原文《C++模板元编程实战》,由李伟先生所著写。


百度网盘链接:


链接:https://pan.baidu.com/s/1e4QIRSDEfCR7_XK6-j-19w

提取码:57GP


在 C++ 中,元函数(metaprogramming)可以操作的数据可以分为以下三类:


类型(type):元函数可以通过特殊的模板技术来操作不同的类型,例如提取类型信息、转换类型等。

值(value):元函数可以通过模板参数进行运算和计算,例如加法、乘法、判断等。这允许在编译时进行一些计算和决策。

表达式(expression):元函数可以通过表达式模板来构建运行时无关的表达式,实现一些高级的编译时计算和优化。

按照书中原文也可以分成以下三类:


类型(Type):元函数可以操作各种类型,包括内置类型(如整数、浮点数、布尔值等)、自定义类型(如结构体、类等)以及模板参数中的类型信息。


值(Value):元函数可以操作编译时已知的常量值,这些值可以是整数、浮点数、布尔值、指针、引用等。


模板(Template):元函数可以操作模板,包括模板参数、模板实例化以及模板元数据。它可以基于模板参数进行编译时计算,并根据不同的模板实例化生成不同的代码。


这两种分类方式的区别在哪里?


第一个分类方式将元函数能够操作的数据划分为类型、值和表达式三类,更加强调了元函数在 C++ 元编程中的应用。其中,类型是元函数最常见的操作对象,可以通过模板技术来获取、转换和操作类型信息。值则是具体的常量值,在编译时可以进行运算和计算。表达式则是通过表达式模板技术构建的运行时无关的表达式,可用于执行高级的编译时计算和优化。这种分类方式更加关注于元函数在编译时期对代码进行操作和计算的能力。


第二个分类方式将元函数能够操作的数据划分为类型、值和模板三类,更加具体地描述了元函数所能操作的数据的类型。类型表示各种类型的数据,值表示已知的常量值,而模板则表示对模板及其参数的操作和处理。这种分类方式更加突出了元函数能够操作的具体数据类型的特点。


所以无论是按照它们的特点还是书中要讲解的内容我们都按照第二种分类方式来即可.


提示:以下是本篇文章正文内容,下面内容主要为个人理解以及少部分正文内容


一、模板作为元函数的输入

书中代码:

template <template> <typename> class T1, typename T2>
struct Fun_ {
    using type = typename T1<T2>::type;
};
template <template <typename> class T1, typename T2>
using Fun = typename Fun_<T1, T2>::type;
Fun<std::remove_reference, int&> h = 3;

1. 在元函数内部定义了一个嵌套类型 type,其类型是 T1<T2>::type。这个嵌套类型是通过模板模板参数 T1 对 T2 进行一次类型转换来获得的


2.定义了一个嵌套类型 type,其类型是 T1<T2>::type。这个嵌套类型是通过模板模板参数 T1 对 T2 进行一次类型转换来获得的


3.Fun<std::remove_reference, int&> 这个表达式实际上等价于 Fun_<std::remove_reference, int&>::type。即,通过 Fun_ 模板类将 std::remove_reference 和 int& 进行组合,并获取其 type 成员类型。


4.最后就是推导成 std::remove_reference<int&>::type 将 int& 类型作为参数传递,而std::remove_reference模板的作用就是移除修饰符,结果就是返回了int类型,用来初始化h的值为3


Fun 是一个典型的高阶函数的解释如下:

在这段代码中, Fun 被定义为一个模板别名,它接受两个模板参数 T1 和 T2,并使用 Fun_ 模板类对这两个参数进行组合。这种将一个函数或者模板作为参数或者返回值的函数称为高阶函数。


二、模板作为元函数的输出

示例代码

#include <iostream>
#include <type_traits>
// 定义模板 RemovePointer,将指针类型去除
template <typename T>
struct RemovePointer {
    using type = T;
};
template <typename T>
struct RemovePointer<T*> {
    using type = T;
};
// 定义元函数模板,输出类型为输入类型的指针类型
template <typename T>
struct AddPointer {
    using type = T*;
};
// 定义元函数模板 Fun_,将 T2 类型通过 T1 元函数转换为新类型
template <template <typename> class T1, typename T2>
struct Fun_ {
    using type = typename T1<T2>::type;
};
// 定义别名模板 Fun,将模板作为元函数的输出类型进行别名化
template <template <typename> class T1, typename T2>
using Fun = typename Fun_<T1, T2>::type;
int main() {
    int a = 10;
    int* b = &a;
    // 将 int 类型转为 int*
    Fun<AddPointer, int> c = a;
    // 移除指针类型
    Fun<RemovePointer, decltype(b)> d = b;
    std::cout << "c: " << c << std::endl;  // 输出: c: 0x...
    std::cout << "d: " << std::is_same<decltype(d), int>::value << std::endl;  // 输出: d: 1
    return 0;
}

1.我们定义了两个元函数模板 `AddPointer` 和 `RemovePointer`,分别将其输入类型转换为指针类型和去除其指针类型。


2.我们定义了 `Fun_` 元函数模板,它接受两个模板参数,第一个参数是一个模板,作为元函数使用,第二个参数是需要转换的类型。 元函数模板 `Fun_` 将类型 `T2` 通过传入的模板转换为新类型,并提供一个类型别名 `type`。


3.我们定义了一个别名模板 `Fun`,使用元函数模板 `Fun_` 将模板类型作为元函数输出类型进行别名化。


在 `main` 函数中,我们声明了一个 `int` 类型的变量 `a` 和一个指向 `a` 的指针 `b`。然后,我们使用 `Fun<AddPointer, int>` 将 `int` 类型转换为指向 `a` 的指针类型,得到的结果存储在变量 `c` 中。接下来,我们使用 `Fun<RemovePointer, decltype(b)>` 从指针类型中去除指针,得到的结果存储在变量 `d` 中。最后我们输出 `c` 和 `d` 的值,可以看到指针被成功转换为了 `int` 型,指针类型也被成功去除,得到的结果都能够正确输出。


三、容器模板

以下内容可结合书中原文学习

长参数模板(Variadic Templates)是C++11引入的特性,它允许定义一个接受任意数量参数的模板函数或类模板。


在传统的C++中,模板参数的数量是固定的,不允许接受可变数量的参数。但是,变长参数模板允许你在模板参数中使用"…"语法,用于表示可变数量的模板参数。


使用变长参数模板,你可以定义接受任意数量参数的模板函数或类模板。这使得编写更加通用的代码成为可能。你可以在模板中使用参数包展开来对参数进行遍历、展开和处理。

C++代码示例

#include <iostream>
// 递归终止函数
void print()
{
    std::cout << std::endl;
}
// 递归展开参数包并打印
template <typename T, typename... Args>
void print(const T& value, Args... args)
{
    std::cout << value << " ";
    print(args...);  // 递归展开参数包
}
int main()
{
    print(1, 2, 3, "Four", 5.6);  // 打印:1 2 3 Four 5.6
    return 0;
}

实现容器代码示例

#include <iostream>
#include <string>
template <typename... T>
class MyContainer
{
public:
    MyContainer()
        : size_(0)
    {
    }
    // 向容器中添加一个或多个元素
    void add(const T&... args)
    {
        (void)std::initializer_list<int>{(elements_[size_++] = args, 0)...};
    }
    // 返回容器中指定位置的元素
    T& get(int index)
    {
        return elements_[index];
    }
    // 返回容器中元素的数目
    int size()
    {
        return size_;
    }
private:
    T elements_[sizeof...(T)];  // 用数组保存元素
    int size_;  // 当前元素数量
};
int main()
{
    MyContainer<int, std::string, double> container;
    container.add(10, "Hello", 3.14);
    container.add(20, "World", 6.28);
    std::cout << "Element 0: " << container.get(0) << std::endl;
    std::cout << "Element 1: " << container.get(1) << std::endl;
    std::cout << "Element 2: " << container.get(2) << std::endl;
    std::cout << "Size: " << container.size() << std::endl;
    return 0;
}

在上面的代码中,模板类MyContainer的模板参数使用了变长参数模板。它使用可变数量的模板参数T...来定义类中保存的元素类型。


该模板类中的add()函数使用了初始化列表递归展开,来添加任意数量的元素。它将所有参数作为一个初始化列表,并递归展开该列表。在展开的过程中,add()函数将元素保存到elements_数组中,并将数组大小size_加1。


get()函数根据参数index返回容器中指定位置的元素。


size()函数用于返回容器中元素的数量。


在主函数中,我们创建了一个MyContainer对象,并通过add()函数添加了两组元素。随后,我们调用get()函数访问容器中的元素,并使用size()函数访问容器大小。


这样就实现了一个简单的容器类,它可以保存任意数量和类型的元素,并提供了基本的操作函数。通过变长参数模板,模板类可以更加通用化,支持更灵活的类型和元素数量。

目录
相关文章
|
2月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
2月前
|
监控 Kubernetes 安全
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !
蒋星熠Jaxonic,技术探索者,以代码为笔,在二进制星河中书写极客诗篇。专注Docker与容器化实践,分享从入门到企业级应用的深度经验,助力开发者乘风破浪,驶向云原生新世界。
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !
|
2月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
5月前
|
缓存 监控 前端开发
告别卡顿!3大前端性能优化魔法 + CSS容器查询实战
告别卡顿!3大前端性能优化魔法 + CSS容器查询实战
254 95
|
6月前
|
运维 监控 数据可视化
容器化部署革命:Docker实战指南
容器化部署革命:Docker实战指南
|
8月前
|
Ubuntu Linux Docker
Docker容器的实战讲解
这只是Docker的冰山一角,但是我希望这个简单的例子能帮助你理解Docker的基本概念和使用方法。Docker是一个强大的工具,它可以帮助你更有效地开发、部署和运行应用。
219 27
|
6月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
235 0
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
184 0
|
9月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!

热门文章

最新文章