非类型模板参数 和 模板型模板参数
整数以及枚举类型;指向对象或者函数的指针;对对象或函数的引用;指向对象成员的指针。统称为非类型模板参数。
模板型模板参数,是指模板参数还可以是一个模板。
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 "); } 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 "); 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 "); } 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 ", array0.id); CArray2<char, 20> array1; printf("ID:%d ", array1.id); CArray2<int, 10> array3; printf("ID:%d ", array3.id); CArray2<int, 20> array4; printf("ID:%d ", 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 ", gwrapper.id); 20 21 wrapper<&global_variable> gwrapper4; 22 printf("ID:%d ", gwrapper4.id); 23 24 wrapper<&global_variable3> gwrapper3; 25 printf("ID:%d ", 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 ", call<&some_value::add_by>(v0, 1));//&是必要的,否则会认为是调用some_value::add_by但是没给参数 printf("%d ", call<&some_value::sub_by>(v0, 2)); printf("%d ", 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(); }