C++模板基础(一)模板基本技巧(上)
关键字typename
C++在标准化过程中,引入了关键字typename来说明模板内部的某个标识符是一种类型:
1 |
|
T::Subtype是定义在T类中的一个类型,这里需要使用typename关键字来特殊注明它是一种类型,若不使用typename关键字,那么编译器会优先匹配T类的非类型成员,如静态数据成员和枚举常量。虽然不一定会出错,但是为了增强代码的可读性和防止可能的错误,只要依赖模板参数的名称是一种类型,就必须使用typename。
这种声明广泛应用于STL中,如:
1 | template <typename T> |
print()函数可接收一种STL容器作为参数,如示例所示,T为std::vector<int>类型,那么it和end就可使用typename T::const_iterator来获取指向容器首尾的只读迭代器。
C++20后,可能存在许多常见情况下,将不再需要typename。
零初始化
对于基础内置类型(如int,double)并不存在可用的默认初始化函数,因此它们的未初始化的局部变量的值将是不确定的。
现在,让我们新建一个类:
1 | template <typename T> |
如果T为简单类型,我们发现并不存在有效的令x初始化的方法。
出于这个原因,我们可以显示调用内置类型的默认构造函数,将它们初始化为0。
| 类型 | 默认初始化值 |
|---|---|
| 数字类型int,double,long long… | 0 |
| bool | false |
| pointer | nullptr |
使用方法
1 | T x{}; |
这种初始化方式称为值初始化,这意味着要么调用本身提供的构造函数,要么用0初始化对象。即时构造函数声明为显式,也可以这么做。
1 | template <typename T> |
在C++11前,确保正确初始化的语句是:
1 | T x = T(); |
在C++17前,只有选择用于拷贝构造函数为非显式时,才能这么做。C++17中的强制拷贝省略解除了这一限制,因而两种语法都可以使用。
从C++11开始,非静态的类成员也可以使用默认初始化:
1 | template <typename T> |
但是默认实参不能使用该语法,必须显式表示出来:
1 | template <typename T> |
使用this->
对于基类依赖于模板参数的类模板,我们使用基类的成员时,必须使用this->。
1 | template <typename T> |
当我们创建一个Derived类实例并且调用foo()函数时,程序报错:error: there are no arguments to 'bar' that depend on a template parameter, so a declaration of 'bar' must be available [-fpermissive]。编译器不会考虑基类中的bar(),而是会选择去外部寻找。我们会在之后详细探讨这个问题的成因,在此之前,要记住基类是模板时,使用this->或者Base<T>::。
处理原始数组和字符串字面量的模板
在我们将原始数组或字符串字面量传递给模板时,一定要注意模板参数是声明为值传递还是引用传递。假如传递int a[]数组,若为引用传递,那么推到类型T不会退化,为int const[];而若为值传递,那么T会退化为int const*。
我们还可以定义专门处理原始数组和字符串字面量的模板:
1 | template <typename T, int N, int M> |
自定义了一个专门处理原始数组和字符串字面量的less<>()函数,用于判断数组或者字符串的字典序大小。

