1.3 模板模板参数

模板的模板参数

template<class T, int i> class A {
int x;
};
template<class T> class A<T, 5> {
short x;
};
template<template<class T> class U> class B1 { };
B1<A> c;

注意,这个代码是无法通过编译的。因为编译器在匹配模板参数的时候,是看全部参数的,不会去考虑A的偏特化版本。

The compiler considers the partial specializations based on a template template argument once you have instantiated a specialization based on the corresponding template template parameter. The following example demonstrates this:

#include <iostream>
#include <typeinfo>
using namespace std;
// 声明一个A的Base模板
template<class T, class U> class A {
public:
int x;
};
// 声明A的偏特化版本
template<class U> class A<int, U> {
public:
short x;
};
template<template<class T, class U> class V> class B {
public:
V<int, char> i;
V<char, char> j;
};
// 那么,在生成代码的时候,实际上生成的是
// class B { A<int, char> i; A <char,char> j; }
B<A> c;
int main() {
cout << typeid(c.i.x).name() << endl;
cout << typeid(c.j.x).name() << endl;
}

如果仔细地看一下B这个类,就会发现,T/U好像没有什么用。因为在B的内部声明的里面从来没有用到这两个类型,如果要用这两个类型应该怎么办?

template<template<class T, class U> class V> class B {
public:
A<T,U> a_;
};

这样是会报错的,因为template<class T, class U> class V只会用来修饰类V而不会用来修饰B。所以在编译的时候,会在B里面说,T/U没有定义。

那么,如果想让B类里面有如下声明:

class B {
public:
T t_;
U u_;
A<T,U> a_;
};

应该怎么办?

应该把B类的声明改成如下:

#include <iostream>
#include <typeinfo>
using namespace std;
template<class T, class U> class A {
public:
T t_;
U u_;
};
template<class U> class A<int, U> {
public:
short x;
};
template<typename T, typename U, typename X, template<typename, typename> class V> class B {
public:
V<T, U> v_;
void print() {
cout << "B" << endl;
}
};
B<int, int, float, A> c;
int main() {
c.print();
}

这里要特别注意看声明。在声明V的时候,所有的这个V会引用到的变量都是在template<typename T, typename U, typename X, ..> B也就是T, U, X这三个里面取。至于具体取哪个,并不能从template的声明里面看出来。

真正要实例化V类的时候,也就是B的内部声明的时候,才会知道V到底是怎么实例化的。

V<T, U> v_;
V<U, X> ux_;

所以可以总结一下,关于模析模板参数。

  • 如果模板模板参数想起作用,那么只能在<typename X, typename Y, typename Z, template<class, class> class V>里面取,并且声明模板板参数的时候,不能写明template<class T, class U> class V,而是需要写成template<class, class> class V这种形式,表示参数都从前面的列表里面选择。
  • 如果是写成template<class T, class U> class V这种格式,并且T/U不在外部列表里面。比如template<typename T, typename U, typename X, template<typename T, typename U> class V>这里会因为T/U重复声明,导到编译不过。那么需要改写成template<typename T, typename U, typename X, template<typename A, typename B> class V> class B {};。但是按照这种写法,模板参数A/B就只能在B类定义的时候内部各种实例化,而不能在外部指定。

所以,也可以总结成。

  • 如果想定义B类的时候,直接也把内部类A的定义决定了。那么,声明的时候,必须采用1.
  • 如果想B类内部自由定义A类的实例化,那么必须采用格式2.

模板模板参数的取值范围

模板模板参数的取值范围可以是:

  • 类型声明在前面,比如<typename X, typename Y, typename Z, template<class, class> class V>。这个时候,类V的模板参数可取范围是X, Y, Z
  • 类型声明在后面,比如<template<class, class> class V, typename X, typename Y, typename Z>也是同样可以取到X/Y/Z的。
template <template<class> class H, class S>
void f(const H<S> &value) {
}

再如

template <template<class, class> class V, class T, class A>
void f(V<T, A> &v) {
// This can be "typename V<T, A>::value_type",
// but we are pretending we don't have it
T temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}

缺省值

template <typename T, template <typename> class Cont = Deque>
class Stack {
//...
};
//...
Stack<int> aStack1; // use default: Cont is Deque
Stack<std::string,List> aStack2; // Cont is List

只是需要注意的是,这里的Deque的声明也需要是template <typename> class Deque { };这样。也就是只有一个模板参数。

注意这里的Deque不是STL里面Deque

无用的模板参数

有时候,如果采用如下声明,会导致相同的结果。

template<typename T>
class Vec { };
template<template<typename T> typename V>
class Wrap { } ;

在这里,Wrap的声明得到的效果就与

template<template<typename T> typename V>
class Wrap { };

得到同样的效果。因为template<template<typename T> typename V>会导致V没有参数范围可以选择。只能在内部指定类型。

同理,可以扩展到多个参数的情况。

template <template <typename Element,
class Allocator> class Cont>
class Wrapper3;

template <template <typename,typename> class Cont>
class Wrapper3;

这两个也应该是等价的。在使用的时候,都需要在

template <template <typename,typename> class Cont>
class Wrapper3 {
Cont<int,std::allocator<int>> cont_;
};

这样的声明方式。