非類型模板參數 和 模板型模板參數
整數以及枚舉類型;指向對象或者函數的指針;對對象或函數的引用;指向對象成員的指針。統稱為非類型模板參數。
模板型模板參數,是指模板參數還可以是一個模板。
1、整數模板參數
非類型模板參數的作用相當於為函數模板或類模板預定義一些常量,在生成模板實例時,也要求必須以常量即編譯期已知的值為非類型模板參數賦值。
//就是模板中有一個參數,但它並不是模板參數,並不會適配不同的類型,而是某種固定的類型 那么他的好處在哪?“模板中聲明的常量,在模板的所有實例中都具有相同的值,而非類型模板參數則對於在不同的模板實例中擁有不同的值來滿足不同的需求”。這句話說得太官方了,我們來看個例子:
template<typename T> class CArray { static cosnt unsigned size = 10; T elems[size]; public: T& operator[](unsigned i) throw (std::out_of_range) { if (i >= size) { throw std::out_of_range("Access out of range\n"); } else { return elems[i]; } } };
這個例子存在什么問題?問題就在於數組的大小被寫死了,這個模板在編譯器只能靈活適配不同的數組類型,但是無法適配不同的數組大小。但是如果改成這樣,就會靈活很多:
1 template<typename T, unsigned Size> 2 class CArray2 3 { 4 T elems[Size]; 5 public: 6 T& operator[](unsigned i) throw (std::out_of_range) 7 { 8 if (i >= size) 9 { 10 throw std::out_of_range("Access out of range\n"); 11 } 12 else 13 { 14 return elems[i]; 15 } 16 } 17 };
讓我們來驗證一下,非類型模板參數Size的值不同,是否產生的是不同的函數實例,稍微改造一下函數如下:
template<typename T, unsigned Size> class CArray2 { public: CArray2() { id++; } ~CArray2(){} T elems[Size]; public: T& operator[](unsigned i) throw (std::out_of_range) { if (i >= size) { throw std::out_of_range("Access out of range\n"); } else { return elems[i]; } } public: static int id; }; template<typename T, unsigned Size> int CArray2<T, Size>::id = 0; //順便我們也應該了解這種帶有非類型模板參數的模板類如何定義一個static成員 void main() { CArray2<char, 20> array0; printf("ID:%d\n", array0.id); CArray2<char, 20> array1; printf("ID:%d\n", array1.id); CArray2<int, 10> array3; printf("ID:%d\n", array3.id); CArray2<int, 20> array4; printf("ID:%d\n", array4.id); getchar(); }
運行結果如下:
2、函數指針模板參數
前面一小節,在模板參數中固定寫死了某種數據類型,這里我們也可以在定義模板時固定寫死某種函數參數類型。而這個函數參數類型又可以適配模板中的模板參數類型。
1 template<typename T, void(*f)(T &v)> 2 void foreach(T array[], unsigned size) 3 { 4 for (unsigned i = 0; i < size; ++i) 5 { 6 f(array[i]); 7 } 8 } 9 10 template<typename T> 11 void inc(T &v){ ++v; } 12 13 template<typename T> 14 void dec(T &v){ --v; } 15 16 template<typename T> 17 void print(T &v){ printf("%d ", v); } 18 19 void main() 20 { 21 int array[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; 22 foreach<int, print<int>>(array, 8); 23 24 foreach<int, inc<int>>(array, 8); 25 26 getchar(); 27 }
3、指針及引用模板參數
只有指向全局變量及外部變量及類靜態變量的指針及引用才可以作為模板參數。函數的局部變量、類成員變量等均不能作為模板參數。因為模板參數值必須是編譯時已知的。
1 template<int* p> 2 struct wrapper 3 { 4 int get(){ return *p; } 5 void set(int v){ *p = v; } 6 7 }; 8 9 template<int &p> 10 struct wrapper2 11 { 12 int get(){ return p; } 13 void set(int v){ p = v; } 14 }; 15 16 int global_variable = 0; 17 18 int main() 19 { 20 wrapper<&global_variable> gwrapper; 21 wrapper2<global_variable> gwrapper2; 22 }
有一個明確的結論是,global_variable 決定了gwrapper的類型。即如果我添加一個
int global_variable3 = 0; 和 wrapper<global_variable3> gwrapper3;
那么gwrapper和gwrapper3並非同一個類型。
之前我們講述的是用typename T來區分兩個模板實例,但是這里的一個指針、一個整形常量(統稱非模板型模板參數)就可以直接區分模板實例:
1 template<int* p> 2 struct wrapper 3 { 4 public: 5 wrapper(){ id++; } 6 int get(){ return *p; } 7 void set(int v){ *p = v; } 8 public: 9 static int id; 10 }; 11 template<int* p> int wrapper<p>::id = 0; 12 13 int global_variable = 0; 14 int global_variable3 = 0; 15 16 int main() 17 { 18 wrapper<&global_variable> gwrapper; 19 printf("ID:%d\n", gwrapper.id); 20 21 wrapper<&global_variable> gwrapper4; 22 printf("ID:%d\n", gwrapper4.id); 23 24 wrapper<&global_variable3> gwrapper3; 25 printf("ID:%d\n", gwrapper3.id); 26 27 getchar(); 28 29 }
4、成員函數指針模板參數
class some_value { int value; public: some_value(int _value) :value(_value){} int add_by(int op){ return value += op; } int sub_by(int op){ return value -= op; } int mul_by(int op){ return value *= op; } }; typedef int (some_value::* some_value_mfp)(int); template<some_value_mfp func> int call(some_value &value, int op){ return (value.*func)(op); }//*是必要的,否則會認為是在使用value類的成員func void main() { some_value v0(0); printf("%d\n", call<&some_value::add_by>(v0, 1));//&是必要的,否則會認為是調用some_value::add_by但是沒給參數 printf("%d\n", call<&some_value::sub_by>(v0, 2)); printf("%d\n", call<&some_value::mul_by>(v0, 3)); getchar(); }
5、模板型模板參數
首先我有三個模板:
template<typename T> struct inc { void operator()(T &v) const { ++v; } }; template<typename T> struct dec { void operator()(T &v) const { --v; } }; template<typename T> struct print { void operator()(T &v) const { std::cout << ' ' << v; } };
這三個模板決定了foreach生成不同的實例(當然還有foreach本身的第二個模板參數),這里注意只有類模板可以作為模板參數,所以這里只能用class而不能用struct:
template<template<typename TT> class Func, typename T> void foreach(T array[], unsigned size) { Func<T> func; for (unsigned i = 0; i < size; i++) { func(array[i]); } }
在foreach中使用第一個模板 or 在foreach中使用第二個模板 or 在foreach中使用第三個模板?都有可能!所以要在foreach中添加一個模板參數用來決定使用哪個模板,這就是模板的模板,也就是模板型模板參數。
void main() { int array[] = { 1, 2, 3, 4, 5, 6, 7 }; foreach<print>(array, 7); foreach<inc>(array, 7); foreach<dec>(array, 7); getchar(); }