模板泛化、模板特化、所占字节数、继承实现模板展开、using循环命名展开可变参数
模板泛化
当前不知道是什么类型,调用时才知道是什么类型
advanced.h
#pragma once
#include <iostream>
using namespace std;//泛化:所有的模板参数类型都未定义,当使用时才知道
template<class T1,class T2/* typename ...ParamTypes*/>//类型和可变参数的类型都不知道
class FHello_Class
{
private:T1 a;T2 b;
};
学习.cpp
#include <iostream>
#include"advanced.h"int main()
{FHello_Class<int, float>A;return 0;
}
模板特化
特化是一种模板技术
模板全特化
必须写一个模板泛化做匹配,否则用不了
模板特化是一种将模板参数替换为具体类型或值的过程
#pragma once
#include <iostream>
using namespace std;//泛化:所有的模板参数类型都未定义,当使用时才知道
template<class T>
class FHello_Class
{
private:T1 a;T2 b;
};//特化(全特化):模板特化是一种将模板参数替换为具体类型或值的过程
template<>
class FHello_Class<int>//给定的类型一定是具体的
{};
/*template<>
class FHello_Class1<int, float>//因为没有对应的模板泛化,所以会报错
{};*///函数全特化
template<class T>
void FunTest()
{};
template<>
void FunTest<int>()
{}
模板特化分为模板全特化和模板偏特化
全特化(Full Specialization)和偏特化(Partial Specialization)是C++中模板特化的两种形式。
全特化是指对完整的模板进行特化,也就是将模板中的所有参数都替换为具体的类型或值。全特化通过使用特定的类型或值提供完整的特化定义。例如:
template<typename T, int N>
class Array
{
public:T elements[N];
};template<>
class Array<int, 5>
{
public:int elements[5];
};
在上面的例子中,我们定义了一个通用的模板类 Array
,包含一个类型参数 T
和一个整数参数 N
。然后我们使用全特化对类型参数为 int
,整数参数为 5
的情况进行了特化。这意味着当我们使用 Array<int, 5>
这个类型时,将使用全特化的定义,即 Array<int, 5>
类型将有一个 int
类型的数组成员。
偏特化是指对模板中的部分参数进行特化。偏特化允许我们针对特定的参数组合提供不同的定义。例如:
template<typename T, typename U>
class Pair
{
public:T first;U second;
};template<typename T>
class Pair<T, T>
{
public:T element;
};
在上面的例子中,我们定义了一个通用的模板类 Pair
,包含两个类型参数 T
和 U
。然后我们使用偏特化对两个类型参数相同的情况进行了特化。这意味着当我们创建 Pair<int, int>
这样的实例时,将使用偏特化的定义,即 Pair<int, int>
类型将只有一个 int
类型的成员 element
。
总之,全特化是对完整的模板进行特化,将所有参数都替换为具体的类型或值;而偏特化是对模板中的部分参数进行特化,允许我们为特定的参数组合提供不同的定义。这两种特化形式使得C++中的模板更加灵活和强大。
通过模板偏特化获取类型所占字节数
偏特化加可变参数
#pragma once
#include <iostream>
using namespace std;//泛化
template<class T,class ... ParamTypes>
class Flen
{
public:enum{Number = Flen<T>::Number + Flen<ParamTypes...>::Number//得到最终类型大小};
};//偏特化
template<class Last>
class Flen<Last>
{
public:enum{Number = sizeof(Last)};
};
#include <iostream>
#include"advanced.h"int main()
{Flen<int, float, double, int>len;//输出结果位20 (4+4+8+4+4)cout << len.Number << endl;return 0;
}
通过模板偏特化和宏获取类型所占字节数
…ParamTypes和ParamTypes…的区别
ParamTypes…:ParamTypes…将被展开为一系列类型参数,并传递给Flen模板的实例化
… ParamTypes:…是展开模板参数包的语法,而不是参数包本身的一部分。class … ParamTypes是用于展开模板参数包ParamTypes的语法
SpawnIndex<3, int, float, double>
在上面的代码中,3 是一个非类型模板参数,int, float, double 是类型模板参数。在 SpawnIndex 的定义中,
…ParamTypes 将类型模板参数完整地展开,将 int, float, double 分别作为一个参数展开。
而 ParamTypes … 将 int, float, double 作为一个整体展开。
advanced.h
#pragma once
#include <iostream>
using namespace std;//泛化
template<class T,class ... ParamTypes>
class Flen
{
public:enum{Number = Flen<T>::Number + Flen<ParamTypes...>::Number//得到最终类型大小};
};//偏特化
template<class Last>
class Flen<Last>
{
public:enum{Number = sizeof(Last)};
};#define A(Name, ...)Flen<__VA_ARGS__>Name;//通过__VA_ARGS__接收
学习.cpp
#include <iostream>
#include"advanced.h"int main()
{Flen<int, float, double, int>len;//输出结果位20 (4+4+8+4+4)cout << len.Number << endl;A(len2, int, float, double, int, long long);//输出结果位28 (4+4+8+4+4+8)cout << len2.Number << endl;return 0;
}
通过继承实现模板展开
using
using单独使用相当于为类或结构体起别名
using Helloc = SpawnIndex<10>::Type;
给类Type起了别名Helloc
advanced.h
#pragma once
#include <iostream>
using namespace std;//定义
template<int ...>
struct HelloIndex
{};//展开的中间值
template<int N,int ...ParamTypes>//N代表最大的,...ParamTypes代表数字
struct SpawnIndex :SpawnIndex<N - 1, N - 1, ParamTypes ...>
{};//终止,如果没有此会无限循环
template<int ... ParamTypes>
struct SpawnIndex<0, ParamTypes ...>
{typedef HelloIndex<ParamTypes...>Type;
};
学习.cpp
#include <iostream>
#include"advanced.h"int main()
{using Helloc = SpawnIndex<10>::Type;//展开一个10,同时为类Type起了一个别名Helloc//using单独使用相当于给类或结构体起别名//查看展开cout << typeid(Helloc).name() << endl;//输出结果struct HelloIndex<0,1,2,3,4,5,6,7,8,9>SpawnIndex<3>::Type;//SpawnIndex<3>::Type;的展开过程// //匹配到struct SpawnIndex :SpawnIndex<N - 1, N - 1, ParamTypes ...>进行运算//struct SpawnIndex :SpawnIndex< 2, 2>// //struct SpawnIndex< 2, 2> :SpawnIndex< 1, 1, 2>// N ParamTypes N N ParamTypes //左边是template<int N,int ...ParamTypes>的形式,右边是SpawnIndex<N - 1, N - 1, ParamTypes ...>// //struct SpawnIndex< 1, 1, 2> :SpawnIndex< 0, 0, 1, 2>//因为N值为0了,展开到这一步会终止,之后匹配struct SpawnIndex<0, ParamTypes ...>此模板,进行下面步骤// //typedef HelloIndex< 0, 1, 2>Type;return 0;
}
(ue4源码的模板综合了很多,如可变参数、特化、非特化)
通过using循环命名的方式来展开可变参数
advanced.h
#pragma once
#include <iostream>
using namespace std;//定义
template<int ...>
struct HelloIndex
{};//通过using展开的中间值
template<int N, int ...ParamTypes>
struct SpawnIndex
{using Type = typename SpawnIndex<N - 1,N - 1, ParamTypes...>::Type;//编译器先发现Type,之后发现后面代码,之后一层层递归,递归过程中参数会改变
};//循环终止
template<int ... ParamTypes>
struct SpawnIndex<0, ParamTypes ...>//最后在参数为0时运行最后模板
{typedef HelloIndex<ParamTypes...>Type;
};
学习.cpp
#include <iostream>
#include"advanced.h"int main()
{using Helloc = SpawnIndex<10>::Type;//查看展开cout << typeid(Helloc).name() << endl;//输出结果:struct HelloIndex<0,1,2,3,4,5,6,7,8,9>return 0;
}
typename 是一个关键字,用于在模板编程中指示一个依赖类型。它通常用于声明模板中的类型参数,用于告知编译器某个名字代表一个类型
问题:为什么又两个N-1
在模板参数SpawnIndex<N, int ...ParamTypes>
中,N - 1
在每次递归调用时作为新的模板参数传递给SpawnIndex
,而N - 1, N - 1
则是作为递归调用的模板参数。这两个参数的作用如下:
首先,在每次递归调用时,N - 1
作为新的模板参数传递给SpawnIndex<N - 1, N - 1, ParamTypes...>::Type
,它用于更新N
的值,实现递减。这样,在每次递归时,N
的值都会减小,直到最后递归到SpawnIndex<0, ParamTypes...>
,从而触发递归终止的模板。
其次,在递归调用中,N - 1, N - 1
作为ParamTypes
的一部分被传递给递归调用的模板参数列表。这样,新的模板参数列表中会包含N - 1, N - 1, ParamTypes...
,并被传递给下一次递归调用。这样,每次递归调用时,ParamTypes
列表都会逐渐增长,记录了调用的历史信息。
综上所述,N - 1
主要用于控制递归调用的次数,而N - 1, N - 1
在递归调用中记录了每次递归的历史信息。