C++ 模板03--基本技巧
C++ template 中关于实践的一些技巧和基础机制
typename
关键字
当一个类型名依赖于一个模板参数时,该类型名之前必须使用 typename
:
1 | template<typename T> |
zero-initialization
对于 C++ 中的基本类型如 int, double, 和 pointer 等类型,它们没有默认构造函数因此不会自动将自己初始化为默认值。在非全局作用域下,未显式初始化的局部变量具有未定义的值:
1 | void func () { |
因此在模板中,如果需要对模板参数提供的类型构造一个对象,必须显式指定为其默认初始化( 大括号初始化 ):
1 | template<typename T> |
大括号初始化意味着:
- 如果对象类型定义有默认构造函数,则调用该默认构造函数,如果没有默认构造函数,则会尝试使用初始化列表构造函数
- 对于内置类型,执行零初始化( zero initialization )。即: 对象的值被初始化为 0 (数值类型,指针) 或 false (bool 类型)
使用 this->
对于继承体系中的类模板,如果基类也是类模板且基类模板依赖于派生类模板的模板参数,那么对于在基类中定义的符号,应该总是使用 this->
或 Base<T>::
标识:
1 | template <typename T> |
原始数组或字符串字面量作参数的模板
当原始数组或字符串字面量作参数时,如果模板的参数类型被声明为引用类型,那么原始数组或字符串字面量不会在传参过程中发生类型退化:
1 | template<typename T, size_t N, size_t M> |
数组也可以以不完整类型的形式出现,即一个数组的声明符号也可以用作模板参数。由于数组声明符号可以不提供长度信息,因此一个支持所有数组类型的模板必须为不完整数组(无边界数组)提供特化:
1 | extern int x[]; // an incomplete type of int [] |
成员模板
- 类/类模板的成员也可以被定义成模板
对于类模板,只有被用到的成员函数才会被实例化
- 类/类模板的成员模板也可以被特化(全特化, 偏特化)
- 类/类模板的特殊成员函数也可以被定义成模板,但是需要注意:
- 构造函数模板或赋值运算符模板不会替换预定义的构造函数或赋值运算符
- 成员模板不算做复制或移动对象的特殊成员函数
.template
关键字-
当调用成员模板的对象其自身依赖于模板参数时,编译器无法将
<
解析成模板参数列表的开始,因此需要显式地使用.template
表示模板构造。例如:1
2
3
4template<unsigned long N>
void printBitset ((std::bitset<N> const& bs)) {
std::cout << bs.template to_string<char, std::char_traits<char>, std::allocator<char>>();
}上例中由于
bs
的类型依赖于模板参数N
, 因此必须用.template
才可以调用成员模板。 -
类似的操作还有
->template
和::template
, 应该仅在模板中使用
-
- 泛型
lambda
表达式是成员模板的 wrapper
C++ 14 中引入的泛型lambda
表达式例如:实际上相当于:1
2
3[] (auto a, auto b) {
return a + b;
};1
2
3
4
5
6
7
8class [InnerCompilerID] { // compiler given name
public:
[InnerCompilerID]();
template <typename T1, typename T2>
auto operator()(T1 a, T2 b) const {
return a + b;
}
};