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++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
63 4
|
2月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
37 3
|
2月前
|
自然语言处理 编译器 Linux
告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
本文中,阿里云智能集团开发工程师李泽政以 Alinux 为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个 C++ 模块工程并使用模块封装第三方库或是改造现有的项目。
|
2月前
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
42 0
|
2月前
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
33 0
|
3月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
24 1
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
63 2
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
113 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
112 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
152 4

热门文章

最新文章