非类型模版参数
非类型参数表示一个值而非一个类型,通过一个特定的类型名而非关键字typename或者class来指定非类型参数。当一个模版被实例化时,非类型参数被一个用户提供或者编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化。(关键在于编译器的自动推断)
例如编写一个compare版本用来处理字符字面常量。这种字面常量是const char的数组。由于不能拷贝数组,所以我们将自己的参数定义为数据的引用。由于我们希望比较不同长度的字符字面常量,因此为模版定义了两个非类型的参数。第一个模版参数表示第一个数组的长度,第二个参数表示第二个数组的长度。
template<unsigned N, unsigned M> int compare(const char (&p1)[N], const char (&p2)[M]) { return strcmp(p1, p2); }
当我们调用compare("hi", "mom")的时候,实际上实例化出如下版本:
int compare(const char (&p1)[3], const char (&p2)[4])
模版编译
为了实例化一个模版,编译器需要掌握模版的声明以及定义,也就是模版的头文件通常既包括声明也包含定义。
在一个类模版的作用域内, 可以直接使用模版名而不必指定模版实参。
类模版和友元素
上图中的前置声明要注意下咯,模版类的前置声明的形式(template <typename> class Blobptr)。
友元的声明用Blob的模形参作为它们自己的模版实参。因此,友好关系被限定在用相同类型实例化的Blobptr与Blob相等运算符中:
Blob<char> ca; // BlobPtr<char> 和 operator==<char>为本对象的友元
通用和特定的模板友好关系
为了让所有实例成为友元,友元声明中必须使用于类模版本身不同的模版参数。
令模版自己的类型参数成为友元
template <typename Type> class Bar { friend Type; //将访问权限授予用来实例化Bar的类型 }
对于某个类型名FOO而言,FOO将成为Bar<Foo>的友元。(静态成员可以通过对象或者作用域运算符调用,静态成员除了声明之外,必须有唯一的定义。)
当我们希望通知编译器一个名字表示类型的时候,必须使用关键字typename,而不能使用class。
模版默认实参与类模版
类模板的成员模板
在类模版外定义成员模版的时候,必须同时为类模版以及成员模版提供模版参数列表(类模版在前,成员模版在后)。
控制实例化
当两个或多个独立编译的源文件使用了相同的模版,并提供了相同的模版参数时,每个文件都会有该模版的一个实例。对于大文件系统而言,在多个文件中实例化相同模板带来的额外开销会非常严重,可以通过显示实例化来解决这一问题:
extern template declaration; // 实例化声明 template declaration; // 实例化定义 // e.g extern template class Blob<string>; // 声明 template int compare(const int&, const int&); // 定义
当编译器遇到extern模版声明时,它不会在本文件中生成实例化的代码,而是从程序其他位置找到该实例化的一个非extern定义(声明)。例如:
Application.cc中extern声明的模版就不会在本文件实例化,但是必须能够在其他文件中找到非extern的定义。
一个类模版的实例化定义会实例化该模版的所有成员,包括内联的成员函数,显示实例化一个类模版的类型,必须能够用于模版的所有成员。
模版实参推断
从函数实参来确定模版实参的过程被称之为模版实参推断。如果一个函数形参的类型使用了模版类型参数,那么它将采用特殊的初始化规则。编译器通常不是对实参进行类型转换,而是生成一个新的模版实例。有以下规则:
1. 顶层const会被忽略(常量指针,很好理解,由于是生成新的实例,自然指向的对象是可以改变的)。
2. 可以将一个非const的引用对象(或指针)传递给一个const的饮用(或指针)形参。
3. (形参非引用类型)一个数组实参可以转换为一个指向其首元素的指针;一个函数实参可以转换为一个该函数类型的指针。
函数显式实参
如下函数定义:
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);
sum函数没有任何函数实参可以用来推断T1,因此每次使用sum时必须为T1提供一个显示模版参数,如下:
auto val3 = sum<long long>(i, lng); // long long sum(int, long)
注意,显示模版实参按由左至右的顺序与对应的模版参数匹配。模版类型参数已经显示指定了的函数实参,可以进行正常的类型转换。
尾置返回类型与类型转换
如下函数:
我们并不知道返回结果的准确类型,但知道是所需类型是所处理的序列的元素类型。可以考虑尾置返回类型结合decltype来确定类型。
如果我们想利用fcn返回元素的拷贝,我们需要利用remove_reference来获得元素类型。我们用
remove_reference<decltype(*debug)>::type
获得beg引用的元素类型,改进的函数如下:
注意,这里的type是一个类的成员,该类依赖于一个模版参数,需要用typename显示的告诉编译器这是一个类型。
引用折叠和右值引用参数
将一个左值传递给函数的右值引用参数,且此右值引用指向模版类型参数(如T&&),编译器推断模版类型参数为实参的左值引用类型。
引用折叠只能应用于间接创建的引用的引用,如类型别名或模版参数。
尽管static_cast只能用于其他合法的类型转换,但有个例外:可以用static_cast显式的将一个左值转换为一个右值引用。
转发(std::forward)(完美保持原有的类型)
如果一个函数参数是指向模板类型参数的右值引用,它对应的实参的const属性和右值/左值属性将得到保持。
当用于一个指向模版参数类型的右值引用函数参数(T&&)时,std::forward会保持实参的所有细节。
函数重载与模版
可变参数模板
一个可变参数模版就是一个接受可变数目参数的模版函数/类。可变数目的参数称为参数包。可以通过sizeof运算符来计算包中有多少元素。
644)