前言
一个深度学习框架的初步实现为例,讨论如何在一个相对较大的项目中深入应用元编程,为系统优化提供更多的可能。
以下是本书的原文《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()函数访问容器大小。
这样就实现了一个简单的容器类,它可以保存任意数量和类型的元素,并提供了基本的操作函数。通过变长参数模板,模板类可以更加通用化,支持更灵活的类型和元素数量。