模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。产生模板的特定类型实例的过程称为实例化。
模板在使用时将进行实例化,类模板在引用实际模板类类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。
类的实例化
模板的定义:
template
public:
Queue (); // default constructor
Type &front (); // return element from head of Queue
const Type &front () const;
void push (const Type &); // add element to back of Queue
void pop(); // remove element from head of Queue
bool empty() const; // true if no elements in the Queue
private:
// ...
};
当定义模板类对象时,
Queue
编译器自动创建名为 Queue 的类。实际上,编译器通过重新编写 Queue 模板,用类型 int 代替模板形参的每次出现而创建 Queue 类。实例化的类就像已经编写的一样:
// simulated version of Queue instantiated for type int
class Queue
public:
Queue(); // this bound to Queue
int &front(); // return type bound to int
const int &front() const; // return type bound to int
void push(const int &); // parameter type bound to int
void pop(); // type invariant code
bool empty() const; // type invariant code
private:
// ...
};
Queue
Queue qi; //error
用模板类定义的类型总是模板实参。例如,Queue 不是类型,而 Queue
函数模板实例化
使用函数模板时,编译器通常会为我们推断模板实参
int main()
{
compare(1, 0); // ok: binds template parameter to int
compare(3.14, 2.7); // ok: binds template parameter to double
return 0;
}
这个程序实例化了 compare 的两个版本:一个用 int 代替 T,另一个用 double 代替 T,实质上是编译器为我们编写了 compare 的这两个实例:
int compare(const int &v1, const int &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } int compare(const double &v1, const double &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; }
模板实参推断
第一个调用 compare(1, 0) 中,实参为 int 类型;第二个调用 compare(3.14, 2.7) 中,实参为 double 类型。从函数实参确定模板实参的类型和值的过程叫做模板实参推断。
1、多个类型形参的实参必须完全匹配
templateint compare(const T& v1, const T& v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } int main() { short si; compare(si, 1024); return 0; }
这个调用是错误的,因为调用 compare 时的实参类型不相同,从第一个实参推断出的模板类型是 short,从第二个实参推断出 int 类型,两个类型不匹配。
如果 compare 的设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义:
// argument types can differ, but must be compatible templateint compare(const A& v1, const B& v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; }
现在用户可以提供不同类型的实参了:
short si;
compare(si, 1024); // ok: instantiates compare(short, int)
2、类型形参的实参的受限转换
考虑下面的 compare 调用:
short s1, s2; int i1, i2; compare(i1, i2); // ok: instantiate compare(int, int) compare(s1, s2); // ok: instantiate compare(short, short)
如果 compare(int, int) 是普通的非模板函数,则第二个调用会匹配那个函数,short 实参将提升(第 5.12.2 节)为 int。因为 compare 是一个模板,所以将实例化一个新函数,将类型形参绑定到 short。
一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:
const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略 const,即,无论传递 const 或非 const 对象给接受非引用类型的函数,都使用相同的实例化。
数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
template
template
T fref(const T&, const T&); // reference arguments
string s1("a value");
const string s2("another value");
fobj(s1, s2); // ok: calls f(string, string), const is ignored
fref(s1, s2); // ok: non const object s1 converted to const reference
int a[10], b[42];
fobj(a, b); // ok: calls f(int*, int*)
fref(a, b); // error: array types don't match; arguments aren't converted to pointers
第一种情况下,传递 string 对象和 const string 对象作为实参,即使这些类型不完全匹配,两个调用也都是合法的。在 fobj 的调用中,实参被复制,因此原来的对象是否为 const 无关紧要。在 fref 的调用中,形参类型是 const 引用,对引用形参而言,转换为 const 是可以接受的转换,所以这个调用也正确。
在第二种情况中,将传递不同长度的数组实参。fobj 的调用中,数组不同无关紧要,两个数组都转换为指针,fobj 的模板形参类型是 int*。但是,fref 的调用是非法的,当形参为引用时(第 7.2.4 节),数组不能转换为指针,a 和 b 的类型不匹配,所以调用将出错。
3、应用于非模板实参的常规转换
用普通类型定义的形参可以使用常规转换
template
{
return op1 + op2;
}
因为 op2 的类型是固定的,在调用 sum 的时候,可以对传递给 op2 的实参应用常规转换:
double d = 3.14; string s1("hiya"), s2(" world"); sum(1024, d); // ok: instantiates sum(int, int), converts d to int sum(1.4, d); // ok: instantiates sum(double, int), converts d to int sum(s1, s2); // error: s2 cannot be converted to int
4、模板实参推断与函数指针
可以使用函数模板对函数指针进行初始化或赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。
例如,假定有一个函数指针指向返回 int 值的函数,该函数接受两个形参,都是 const int 引用,可以用该指针指向 compare 的实例化
templateint compare(const T&, const T&); // pf1 points to the instantiation int compare (const int&, const int&) int (*pf1) (const int&, const int&) = compare;
pf1 的类型是一个指针,指向“接受两个 const int& 类型形参并返回 int 值的函数”,形参的类型决定了 T 的模板实参的类型,
T 的模板实参为 int 型,指针 pf1 引用的是将 T 绑定到 int 的实例化。
如果不能从函数指针类型确定模板实参,就会出错。例如,假定有两个名为 func 的函数,每个函数接受一个指向函数实参的指针。func 的第一个版本接受有两个 const string 引用形参并返回 string 对象的函数的指针,func 的第二个版本接受带两个 const int 引用形参并返回 int 值的函数的指针,不能使用 compare 作为传给 func 的实参:
// overloaded versions of func; each take a different function pointer type void func(int(*) (const string&, const string&)); void func(int(*) (const int&, const int&)); func(compare); // error: which instantiation of compare?
函数模板的显式实参
在某些情况下,不可能推断模板实参的类型。当函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现这一问题。在这种情况下,有必要覆盖模板实参推断机制,并显式指定为模板形参所用的类型或值
指定显式模板实参
考虑下面的问题。我们希望定义名为 sum、接受两个不同类型实参的函数模板,希望返回类型足够大,可以包含按任意次序传递的任意两个类型的两个值的和,怎样才能做到?应如何指定 sum 的返回类型?
// T or U as the returntype?
template
在这个例子中,答案是没有一个形参在任何时候都可行,使用任一形参都一定会在某些时候失败:
解决这一问题的一个办法,可能是强制 sum 的调用者将较小的类型强制转换为希望作为结果使用的类型:
// ok: now either T or U works as return type
int i; short s;
sum(static_cast
在返回类型中使用类型形参
指定返回类型的一种方式是引入第三个模板形参,它必须由调用者显式指定:
// T1 cannot be deduced: it doesn't appear in the function parameter list templateT1 sum(T2, T3);
这个版本增加了一个模板形参以指定返回类型。只有一个问题:没有实参的类型可用于推断 T1 的类型,相反,调用者必须在每次调用 sum 时为该形参显式提供实参。
为调用提供显式模板实参与定义类模板的实例很类似,在以逗号分隔、用尖括号括住的列表中指定显式模板实参。显式模板类型的列表出现在函数名之后、实参表之前:
// ok T1 explicitly specified; T2 and T3 inferred from argument types
long val3 = sum
显式模板实参从左至右对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推。假如可以从函数形参推断,则结尾(最右边)形参的显式模板实参可以省略。如果这样编写 sum 函数:
// poor design: Users must explicitly specify all three template parameters
template
T3 alternative_sum(T2, T1);
则总是必须为所有三个形参指定实参:
// error: can't infer initial template parameters
long val3 = alternative_sum
// ok: All three parameters explicitly specified
long val2 = alternative_sum
可以使用显式模板实参的另一个例子是有二义性程序,通过使用显式模板实参能够消除二义性
template
// overloaded versions of func; each take a different function pointer type
void func(int(*) (const string&, const string&));
void func(int(*) (const int&, const int&));
像前面一样,需要在调用中传递 compare 实例给名为 func 的重载函数。只查看不同版本 func 的形参表来选择传递 compare 的哪个实例是不可能的,两个不同的实例都可能满足该调用。显式模板形参需要指出应使用哪个 compare 实例以及调用哪个 func 函数。
func(compare