模板泛化、模板特化、所占字節數、繼承實現模板展開、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
在遞歸調用中記錄了每次遞歸的歷史信息。