目录
- C++ 元编程
- 1. 术语
- 2. 元函数
- 1. 数值元函数
- 示例:阶乘计算
- 2. 类型元函数
- 示例:类型选择
- 3. 混合编程
- 1. 常规的计算点积范例
- 2. 混合元编程计算点积
- 4. typelist实现
- 设计和基本操作接口(算法)
- 完整代码
- 5. tuple 实现
- 基础知识
- 1. 左值、右值、左值引用、右值引用
- 2. std::move 究竟做了什么
- 3. std::forward 究竟做了什么
- 4. 万能引用(转发引用)
- 5. 完美转发
- tuple 实现示例
C++ 元编程
元编程的主要目的在于将各种计算从运行期提前至编译期进行,以实现程序运行时的性能提升。也正因如此,元编程是一种增加程序的编译时间,从而提升程序运行效率的编程技术。
在元编程中,涉及许多与循环相关的代码。传统编程中,循环通常采用 for
、while
等语句实现,这些语句一般针对的是运行期的条件变量;而在元编程中,更多的操作其实是针对类型或常量的。这种循环的实现往往会采用递归的手段,使得编译器能够在编译期间完成某些计算。
1. 术语
元编程的英文名称是 Meta Programming,有时也称为 模板元编程(Template Metaprogramming)。可以理解为一种编程手法,用于实现一些比较特殊的功能。元编程与“递归”这一概念紧密相连,代表着在元编程中,大多数情况下都会使用递归编程技术。
模板编程主要应用在泛型编程和元编程:
泛型编程(Generic Programming)强调的是“通用”的概念,旨在通过抽象和参数化来实现代码的复用与灵活性。在泛型编程中,程序员可以编写与类型无关的算法和数据结构,使得同一段代码能够适用于多种类型。模板的设计初衷正是为了满足这一需求。
元编程(Meta Programming)则是一种更高级的编程技巧,旨在通过编译期间的计算和推导来实现某些功能。元编程允许程序员在编译期进行复杂的逻辑处理,通常这些逻辑在运行时才能完成。
2. 元函数
传统的函数都是在程序运行期间被调用和执行的函数,而元函数是能在程序编译期间被调用和执行的函数(编译期间就能得到结果)。引入元函数概念的目的是支持元编程,而元编程的核心也正是元函数。
1. 数值元函数
数值元函数是在编译期间对数值进行计算的元函数。这类元函数可以接收整型常量作为模板参数,并根据这些常量进行编译时计算。数值元函数通常用于实现一些数学运算,如阶乘、斐波那契数列等。
示例:阶乘计算
template<int N>
struct Factorial {static const int value = N * Factorial<N - 1>::value;
};template<>
struct Factorial<0> {static const int value = 1;
};// 使用
constexpr int fact5 = Factorial<5>::value; // fact5 的值为 120
std::cout << fact5 << std::endl; // 输出 120
2. 类型元函数
类型元函数则是针对类型进行操作的元函数。它们允许程序员在编译期间对类型进行推导、选择和变换。这类元函数非常适合于类型特征提取和类型转换等场景。
示例:类型选择
template<bool Condition, typename TrueType, typename FalseType>
struct Conditional;template<typename TrueType, typename FalseType>
struct Conditional<true, TrueType, FalseType> {using type = TrueType;
};template<typename TrueType, typename FalseType>
struct Conditional<false, TrueType, FalseType> {using type = FalseType;
};// 使用
using ResultType = Conditional<true, int, double>::type; // ResultType 的类型为 int
3. 混合编程
混合编程是结合了运行时和编译时计算的技术,允许程序员在编译期间进行一些运算,同时时间复杂度也得到优化。混合编程的关键在于利用模板和递归来处理类型和数值,使得某些操作可以在编译期完成,从而提高运行时性能。
1. 常规的计算点积范例
混合元编程方面,一个比较典型的案例是计算两个向量(数组)点积。
(1)数组a有3个元素a[0]、a[1]、a[2],值分别为1、2、3;
(2)数组b有3个元素b[0]、b[1]、b[2],值分别为4、5、6;
(3)a和b的点积是一个数值,结果为a[0]×b[0] + a[1] ×b[1] + a[2] ×b[2] =1×4+2×5+3×6=32。
在传统编程中,计算两个数组(向量)的点积通常使用循环结构,如for
或while
。以下是一个简单的点积计算示例:
#include <iostream>template<typename T, int U>
auto DotProduct(T* array1, T* array2)
{T dpresult = T{};for (int i = 0; i < U; ++i){dpresult += array1[i] * array2[i];}return dpresult;
}int main()
{int a[] = { 1,2,3 };int b[] = { 4,5,6 };int result = DotProduct<int, 3>(a, b);std::cout << result << std::endl; // 输出 32return 0;
}
2. 混合元编程计算点积
通过元编程,可以在编译期间计算点积。下面的代码展示了如何使用模板和递归实现这一点:
#include <iostream>//泛化版本
template<typename T, int U> //T:元素类型,U:数组大小
struct DotProduct
{static T result(const T* a, const T* b){return (*a) * (*b) +DotProduct<T, U - 1>::result(a + 1, b + 1);}
};
//特化版本,作为递归调用的出口
template<typename T>
struct DotProduct<T, 0>
{static T result(const T *,const T*){return T{};}
};int main()
{int a[] = { 1,2,3 };int b[] = { 4,5,6 };int result = DotProduct<int, 3>::result(a, b);std::cout << result << std::endl;return 0;
}
- 泛化版本:
DotProduct
是一个模板类,它接受元素类型T
和数组大小U
作为模板参数。result
函数是一个静态成员函数,通过递归调用来计算点积。它取当前元素*a
和*b
的乘积,并加上对下一个元素的递归调用。
- 特化版本:
- 当
U
为0
时,递归调用的出口到达。此时返回一个默认初始化的值T{}
,表示点积计算的结束。
- 当
4. typelist实现
typelist的解释为:用来操作一大堆类型的C++容器,就像C++标准库中的list容器能够为数值提供各种基本操作一样(只不过这里操作的不是数值,而是类型)。
从实现上来讲,typelist是一个类模板,译为“类型列表”,这个类模板用来表示一个列表,这个列表中存放着一堆类型。
设计和基本操作接口(算法)
Typelist 的基本定义
template<typename... Types>
struct TypeList {};// 基本类型操作
using EmptyTypeList = TypeList<>; // 空类型列表
1. 取得typelist中的第1个元素(front)
这个操作返回 typelist
中的第一个类型。通过模板特化,可以在编译时获取到这个类型。该操作通常用于获取类型链的起始类型,以便后续处理。
template<typename TList>
struct Front;template<typename Head, typename... Tail>
struct Front<TypeList<Head, Tail...>> {using type = Head; // 返回第一个元素
};
2. 取得typelist容器中元素的数量(size)
这个操作计算并返回 typelist
中包含的类型数量。它使用可变参数模板的特性,通过 sizeof...
来计算类型的数量。
template<typename TList>
struct Size;template<typename... Types>
struct Size<TypeList<Types...>> {static const size_t value = sizeof...(Types); // 元素计数
};
3. 从typelist中移除第1个元素(pop_front)
此操作会返回一个新的 typelist
,该列表中不再包含第一个类型。它通过递归去掉开头的类型,为后续操作提供了简化的列表。
template<typename TList>
struct PopFront;template<typename Head, typename... Tail>
struct PopFront<TypeList<Head, Tail...>> {using type = TypeList<Tail...>; // 移除第一个元素
};
4. 向typelist的开头和结尾插入一个元素(push_front和push_back)
- push_front: 将新类型插入到
typelist
的开头,产生一个新的typelist
。 - push_back: 将新类型插入到
typelist
的结尾,同样产生一个新的typelist
。
template<typename TList, typename NewType>
struct PushFront;template<typename... Types, typename NewType>
struct PushFront<TypeList<Types...>, NewType> {using type = TypeList<NewType, Types...>; // 插入到开头
};template<typename TList, typename NewType>
struct PushBack;template<typename... Types, typename NewType>
struct PushBack<TypeList<Types...>, NewType> {using type = TypeList<Types..., NewType>; // 插入到结尾
};
5. 替换typelist的开头元素(replace_front)
此操作允许替换 typelist
的第一个类型为一个新的类型,并返回一个新的 typelist
。这对于动态更改类型列表的开头非常有用。
template<typename TList, typename NewType>
struct ReplaceFront;template<typename... Tail, typename NewType>
struct ReplaceFront<TypeList<Tail...>, NewType> {using type = TypeList<NewType, Tail...>; // 替换开头元素
};
6. 判断typelist是否为空(is_empty)
此操作检查 typelist
是否包含任何类型。如果列表为空,返回 true
,否则返回 false
。这是确保操作安全性的重要步骤。
template<typename TList>
struct IsEmpty;template<>
struct IsEmpty<EmptyTypeList> {static const bool value = true; // 空类型列表
};template<typename... Types>
struct IsEmpty<TypeList<Types...>> {static const bool value = false; // 非空类型列表
};
7. 根据索引号查找typelist的某个元素(find)
此操作通过索引查找 typelist
中的特定类型。它使用递归进行索引减小,直到找到目标索引的类型。这使得可以根据位置快速访问特定类型。
template<typename TList, size_t Index>
struct Find;template<typename Head, typename... Tail>
struct Find<TypeList<Head, Tail...>, 0> {using type = Head; // 找到第一个元素
};template<typename Head, typename... Tail, size_t Index>
struct Find<TypeList<Head, Tail...>, Index> {using type = typename Find<TypeList<Tail...>, Index - 1>::type; // 递归查找下一个元素
};
8. 遍历typelist找到sizeof值最大的元素(get_maxsize_type)
该操作遍历 typelist
中的所有类型,并返回 sizeof
值最大的类型。它利用条件选择,递归比较每个类型的大小,确定最大值。这在需要根据类型大小进行处理时非常有用。
template<typename TList>
struct GetMaxSizeType;template<typename Head, typename... Tail>
struct GetMaxSizeType<TypeList<Head, Tail...>> {using type = typename std::conditional<(sizeof(Head) >= sizeof(typename GetMaxSizeType<TypeList<Tail...>>::type)),Head,typename GetMaxSizeType<TypeList<Tail...>>::type>::type; // 使用 std::conditional 比较
};// 特化版本,处理空类型列表
template<>
struct GetMaxSizeType<EmptyTypeList> {using type = void; // 空类型列表没有最大类型
};
完整代码
#include <iostream>
#include <type_traits>template<typename... Types>
struct TypeList {};using EmptyTypeList = TypeList<>;// 取得第一个元素
template<typename TList>
struct Front;template<typename Head, typename... Tail>
struct Front<TypeList<Head, Tail...>> {using type = Head;
};// 取得元素数量
template<typename TList>
struct Size;template<typename... Types>
struct Size<TypeList<Types...>> {static const size_t value = sizeof...(Types);
};// 移除第一个元素
template<typename TList>
struct PopFront;template<typename Head, typename... Tail>
struct PopFront<TypeList<Head, Tail...>> {using type = TypeList<Tail...>;
};// 向开头插入一个元素
template<typename TList, typename NewType>
struct PushFront;template<typename... Types, typename NewType>
struct PushFront<TypeList<Types...>, NewType> {using type = TypeList<NewType, Types...>;
};// 向结尾插入一个元素
template<typename TList, typename NewType>
struct PushBack;template<typename... Types, typename NewType>
struct PushBack<TypeList<Types...>, NewType> {using type = TypeList<Types..., NewType>;
};// 替换开头元素
template<typename TList, typename NewType>
struct ReplaceFront;template<typename... Tail, typename NewType>
struct ReplaceFront<TypeList<Tail...>, NewType> {using type = TypeList<NewType, Tail...>;
};// 判断是否为空
template<typename TList>
struct IsEmpty;template<>
struct IsEmpty<EmptyTypeList> {static const bool value = true;
};template<typename... Types>
struct IsEmpty<TypeList<Types...>> {static const bool value = false;
};// 根据索引查找元素
template<typename TList, size_t Index>
struct Find;template<typename Head, typename... Tail>
struct Find<TypeList<Head, Tail...>, 0> {using type = Head; // 找到第一个元素
};template<typename Head, typename... Tail, size_t Index>
struct Find<TypeList<Head, Tail...>, Index> {using type = typename Find<TypeList<Tail...>, Index - 1>::type; // 递归查找下一个元素
};// 遍历找到最大 sizeof 的类型
template<typename TList>
struct GetMaxSizeType;template<typename Head, typename... Tail>
struct GetMaxSizeType<TypeList<Head, Tail...>> {using type = typename std::conditional<(sizeof(Head) >= sizeof(typename GetMaxSizeType<TypeList<Tail...>>::type)),Head,typename GetMaxSizeType<TypeList<Tail...>>::type>::type; // 使用 std::conditional 比较
};// 特化版本,处理空类型列表
template<>
struct GetMaxSizeType<EmptyTypeList> {using type = void; // 空类型列表没有最大类型
};// 测试
int main() {using MyTypes = TypeList<int, double, char>;std::cout << "Size: " << Size<MyTypes>::value << std::endl; // 输出 3std::cout << "Is empty: " << IsEmpty<MyTypes>::value << std::endl; // 输出 0std::cout << "First element type: " << typeid(Front<MyTypes>::type).name() << std::endl; // 输出 intusing Popped = typename PopFront<MyTypes>::type;std::cout << "Size after pop: " << Size<Popped>::value << std::endl; // 输出 2using PushedFront = typename PushFront<MyTypes, float>::type;std::cout << "Size after push front: " << Size<PushedFront>::value << std::endl; // 输出 4using PushedBack = typename PushBack<MyTypes, long>::type;std::cout << "Size after push back: " << Size<PushedBack>::value << std::endl; // 输出 4using FoundType = typename Find<MyTypes, 1>::type; // 查找索引1的类型std::cout << "Found type at index 1: " << typeid(FoundType).name() << std::endl; // 输出 doubleusing MaxSizeType = typename GetMaxSizeType<MyTypes>::type; // 查找最大 sizeof 的类型std::cout << "Max size type: " << typeid(MaxSizeType).name() << std::endl; // 输出 doublereturn 0;
}
- TypeList: 用于存储类型的模板类。
- 基本操作: 提供了如获取第一个元素、计算大小、移除元素、插入元素、替换元素和判断是否为空等基本操作。
Find
: 通过递归查找指定索引的类型。GetMaxSizeType
: 通过比较各类型的sizeof
值,找出最大类型。- 使用示例: 在
main
函数中演示了如何使用这些操作。
5. tuple 实现
基础知识
1. 左值、右值、左值引用、右值引用
-
左值和右值:
- 左值 (lvalue): 表示一个可以被取地址的对象,具有持久的内存位置。例子:
int i = 10;
这里的i
是左值。 - 右值 (rvalue): 表示一个临时的对象,通常是不能取地址的,存在于表达式的右边。例子:
10
是右值。
- 左值 (lvalue): 表示一个可以被取地址的对象,具有持久的内存位置。例子:
-
左值引用:
- 通过左值引用 (
&
) 可以引用一个左值。例子:int &j = i; // j 是左值引用
- 左值引用只能绑定到左值上。尝试将右值绑定到左值引用会导致编译错误,但
const
左值引用可以绑定到右值:const int &j = 10; // 合法
- 通过左值引用 (
-
右值引用:
- 右值引用使用
&&
进行声明,允许绑定到右值。 - 例如:
int &&k = 10; // k 是右值引用
- 右值引用只能绑定到右值。尝试将左值绑定到右值引用会导致编译错误。
- 右值引用使用
-
普通变量:
- 普通的变量(如
int m;
)既不是左值引用也不是右值引用,因为引用必须带有&
或&&
修饰符。
- 普通的变量(如
2. std::move 究竟做了什么
-
std::move
是一个标准库函数,它将左值转换为右值,从而允许使用右值引用。例如:int &&k = std::move(i); // 将左值 i 转换为右值
-
重要的是,
std::move
不会执行任何移动操作,它只是一种类型转换,标记一个对象可以被“移动”。
3. std::forward 究竟做了什么
-
std::forward
是用于实现完美转发的工具,它允许保持参数的值类别(lvalue 或 rvalue)。在模板中使用时,可以根据传入的参数类型决定是保持为左值还是右值。template<typename T> void func(T&& arg) {// 完美转发process(std::forward<T>(arg)); }
4. 万能引用(转发引用)
- 万能引用是指在模板参数中使用的引用类型,可以绑定到左值或右值(例如
T&&
,其中 T 是模板参数)。这种引用在模板中被称为转发引用。
5. 完美转发
- 完美转发使得函数可以根据实际传入的参数类型选择合适的引用类型,从而避免不必要的复制和保证性能。例如,结合
std::forward
和万能引用,可以实现完美转发。
tuple 实现示例
C++ 标准库中的 std::tuple
是一个可以存储不同类型的元素的容器,以下是一个简单的自定义 tuple
实现示例:
tuple
是一种灵活的数据结构,能够存储不同类型的元素。理解左值、右值及其引用非常重要,因为tuple
的实现涉及到对象的生命周期管理、内存效率以及函数调用方式(如移动语义和转发)。std::move
和std::forward
是实现高效代码的重要工具,特别是在模板编程和泛型编程中。
#include <iostream>
#include <utility>
#include <type_traits>template<typename... Types>
class MyTuple;// 特化基础情况
template<>
class MyTuple<> {};// 递归定义
template<typename Head, typename... Tail>
class MyTuple<Head, Tail...> : private MyTuple<Tail...> {
public:Head head; // 当前元素using MyTuple<Tail...>::head; // 继承下一层的头部元素MyTuple(Head h, Tail... t): head(h), MyTuple<Tail...>(t...) {} // 构造函数
};// 获取元素
template<size_t Index, typename Tuple>
struct TupleElement;template<typename Head, typename... Tail>
struct TupleElement<0, MyTuple<Head, Tail...>> {using type = Head; // 返回当前头部元素
};template<size_t Index, typename Head, typename... Tail>
struct TupleElement<Index, MyTuple<Head, Tail...>> {using type = typename TupleElement<Index - 1, MyTuple<Tail...>>::type; // 递归获取
};// 获取元素的辅助函数
template<size_t Index, typename... Types>
typename TupleElement<Index, MyTuple<Types...>>::type& get(MyTuple<Types...>& tuple) {return static_cast<typename TupleElement<Index, MyTuple<Types...>>::type&>(tuple);
}int main() {MyTuple<int, double, char> myTuple(1, 2.5, 'c');std::cout << "First element: " << get<0>(myTuple) << std::endl; // 输出 1std::cout << "Second element: " << get<1>(myTuple) << std::endl; // 输出 2.5std::cout << "Third element: " << get<2>(myTuple) << std::endl; // 输出 creturn 0;
}