一、定义一个简单的函数模板
下面的这个例子就定义了一个模板函数,它会返回两个参数中最大的那一个:
<span style="color:#333333"><code><span style="color:#008000">// 文件:"max.hpp"</span>
<span style="color:#0000ff">template</span><<span style="color:#0000ff">typename</span> T>
<span style="color:#0000ff">inline</span> <span style="color:#0000ff">const</span> T& <span style="color:#a31515">max</span>(<span style="color:#0000ff">const</span> T& x, <span style="color:#0000ff">const</span> T& y)
{
<span style="color:#0000ff">return</span> x < y ? y : x;
}</code></span>
这个函数模板定义了一个“返回两个值中最大者”的函数家族,而参数的类型还没有确定,用类型模板参数T
来确定。模板参数需要使用如下的方式来声明:
<span style="color:#333333"><code><span style="color:#0000ff">template</span>< 模板参数列表 ></code></span>
在这个例子中,模板参数列表为:typename T
。关键字typename
引入了T
这个类型模板参数。当然了,可以使用任何标识符作为类型模板参数的名称。我们可以使用任何类型(基本数据类型、类类型)来实例化该函数模板,只要所使用的数据类型提供了函数模板中所需要的操作即可。例如,在这个例子中,类型T
需要支持operator <
,因为a和b就是通过这个操作符来比较大小的。
鉴于历史原因,也可以使用关键字class
来取代typename
来定义类型模板参数,然而应该尽可能地使用typename
。
二、使用函数模板
下面的程序使用了上面定义的这个函数模板:
<span style="color:#333333"><code><span style="color:#2b91af">#include <iostream></span>
<span style="color:#2b91af">#include <string></span>
<span style="color:#2b91af">#include "max.hpp"</span>
<span style="color:#0000ff">using</span> <span style="color:#0000ff">namespace</span> <span style="color:#0000ff">std</span>;
<span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span> *argv[])
{
<span style="color:#0000ff">cout</span> << max(4, 3) << <span style="color:#0000ff">endl</span>; <span style="color:#008000">// 使用int类型实例化了函数模板,并调用了该函数实例。</span>
<span style="color:#0000ff">cout</span> << max(4.0, 3.0) << <span style="color:#0000ff">endl</span>; <span style="color:#008000">// 使用double类型实例化了函数模板,并调用了该函数实例。</span>
<span style="color:#0000ff">cout</span> << max(<span style="color:#0000ff">string</span>(<span style="color:#a31515">"hello"</span>), <span style="color:#0000ff">string</span>(<span style="color:#a31515">"world"</span>)) << <span style="color:#0000ff">endl</span>; <span style="color:#008000">// 使用string类型实例化了函数模板,</span>
<span style="color:#008000">// 并调用了该函数实例。</span>
<span style="color:#0000ff">return</span> 0;
}</code></span>
通常而言,并不是把模板编译成一个可以处理任何类型的单一实体,而是针对于实例化函数模板参数的每种类型,都从函数模板中产生出一个独立的函数实体。因此,针对于每种类型,模板代码都被编译了一次。这种用具体类型代替模板参数的过程,叫做模板的实例化。它产生了一个新的函数实例(与面向对象程序设计中的实例化不同)。
如果试图基于一个不支持模板内部所使用的操作的类型实例化一个模板,那么将会引发一个编译期错误:
<span style="color:#333333"><code><span style="color:#0000ff">std</span>::<span style="color:#0000ff">complex</span><<span style="color:#0000ff">double</span>> c1, c2;
max(c1, c2); <span style="color:#008000">// 编译错误:std::complex并不支持运算符<</span></code></span>
所以说:模板被编译了两次,分别发生于:
- 模板实例化之前,查看语法是否正确,此时可能会发现遗漏的分号等。
- 模板实例化期间,检查模板代码, 查看是否所有的调用都有效。此时可能会发现无效的调用,例如实例化类型不支持某些函数调用等。
所以这引发了一个重要的问题:当使用函数模板并且引发模板实例化时,编译器必须查看模板的定义。事实上,这就不同于普通的函数,因为对于普通的函数而言,只要有函数的声明(甚至不需要定义),就可以顺利地通过编译期。