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++
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
1月前
|
存储 Ubuntu 关系型数据库
《docker基础篇:7.Docker容器数据卷》包括坑、回顾下上一讲的知识点,参数V、是什么、更干嘛、数据卷案例
《docker基础篇:7.Docker容器数据卷》包括坑、回顾下上一讲的知识点,参数V、是什么、更干嘛、数据卷案例
58 13
|
3月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
111 4
|
3月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
47 3
|
3月前
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
45 0
|
4月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
35 1
|
4月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
92 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
68 19
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
50 13