Post

Template

成员模板( Member Template )

C++ 模板, 除开较为常见的 class templatefunction template, 还有一种 member template. 它被大量运用在继承关系的构造函数中, 以实现通过派生类的拷贝构造基类函数.

1
2
3
4
5
6
7
8
9
10
11
12
// class template
template <class T1, class T2>
struct pair
{
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;
    // function & member template: 特指构造函数的形参类型与类的类型不一样
    template <class U1, class U2>
    pair(const pair<U1, U2> &p) : first(p.first), second(p.second) {}
};

可以通过以下代码来理解, 什么是”通过派生类的拷贝构造基类函数”

1
2
3
4
5
6
7
8
9
10
11
// declaration
class base1 {...};
class derive1 : public base1 {...};

class base2 {...};
class derive2 : public base2 {...};

// correct: up-cast
pair<base1, base2> p(pair<derive1, derive2>());
// error
pair<derive1, derive2> p(pair<base1, base2>());

up-cast 向上映射, 指的是派生类指针向上( 继承关系箭头 )映射为基类. 如 base1 *p = new derive1();. 因此就有了模板成员的诞生, 模板成员的这种做法能够实现向上映射, 因此这也是 STL 中对容器( Container )的设计.

(全)特化( Specialization )

特化与泛化相对, 指的就是指定一个特定的类型, 用 STL 中的 hash 举例. 当同时存在泛化与特化时, 优先使用特化代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
// generalization
template <class Key>
struct hash { };
// specialization
template <>
struct hash <char>
{ ... };
template <>
struct hash <int>
{ ... };
template <>
struct hash <long>
{ ... };

偏特化( Partial Specialization )

  • 参数个数上的偏特化: 多个模板参数绑定其中部分的情况称为偏特化( 部分特化 )
1
2
3
4
5
6
7
// generalization
template <typename T, typename Alloc=...>
class vector { ... };
// partial specialization: bool 被绑定
template <typename Alloc=...>
class vector<bool, Alloc>
{ ... };

写 python 的时候有遇到过偏特化的报错, 如 train(dir='./', model, saveDir='') 是错误的. 偏特化的参数必须连续, 不能中间间隔中断. C++也一样.

  • 参数范围上的偏特化: 任意类型偏特化为任意类型的指针
1
2
3
4
5
6
// generalization
template <typename T>
class myclass { ... };
// partial specialization
template <typename U>
class myclass<U*> { ... };

这里偏特化模板参数之所以用 U, 只是为了说明与泛化类型毫无关系

嵌套模板参数( 模板模板参数 Template Template Parameter )

代码表述更清晰一点, 直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// declare
template <typename T, template <typename T> class Container>
class XCLs
{
private:
    Container<T> c;
    ...
};
// use
template <typename T>
// wrong
XCLs<string, list> wrongList;
// correct
XCLs<string, list<T, allocator<T>>> mylist;

只有在 template 的参数中, typenameclass 作用相同

这里需要注意的是嵌套模板参数的使用语法, 直接通过 list 给容器赋值, 试图使用 list 的默认模板参数( 比如 allocator )赋值, 然而编译器不通过会报错, 必须指明第二个模板参数. 针对需要接受多个模板参数的类时, 调用这种类似嵌套模板的类时, 需要指明所有模板参数.

因此, 若一个类只接受一个模板参数时, 那么就可以编译通过, 如下:

1
2
3
4
5
6
7
8
9
10
template <typename T, template <typename T> class SmartPtr>
class XCLs
{
private:
    SmartPtr<T> ptr;
    ...
};
// correct: 以下 shared_ptr 与 auto_ptr 模板只接受一个参数
XCLs<string, shared_ptr> p1;
XCLs<string, auto_ptr> p1;

与绑定类型的模板参数的区别

先看代码, 判断以下代码是否是嵌套模板参数

1
2
3
4
5
template <class T, class Sequence = deque<T>>
class stack { ... };
// use
stack<int> stk;
stack<int, list<int>> listStk;

实际上, 上述代码不是嵌套模板参数. 原因可以参见使用的代码, 在使用时已经确定了 stk & listStk 绑定的参数. 与前文依旧未绑定参数不同.

C++11: 变参模板( Variadic Template )

C++11引入的新特性, 使得模板参数的个数可变, 不再是确定个数, 以递归打印为例, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 省略 << 重载
ostream& operator<< (ostream& os, myclass &m) {};

void print() {}
template <typename T, typename... Types>
void print(const T &firstArg, const Types &...args)
{
    cout << firstArg << endl;
    print(args...);
}
// use
print(1, 0.5, "hello world", myclass(5,5,5));

print 每次将诸多参数拆分为 1 + pack, 每次输出拆出来的那一个参数, 当 pack 内无参数时, 调用最上面重载的无参数 print. 当然这里只是针对递归输出举例, 可以对拆出来的一个参数与剩下的包进行你需要的任何操作与实现.

此处代码中的 ... 不再是其余篇章中为了省略实现的省略号, 而是非常重要的语法

可以通过 sizeof...(args) 来得知包中的参数个数

This post is licensed under CC BY 4.0 by the author.