模板本质上是半类型安全的宏,因此存在限制。
普通(非模板)函数可以编译为驻留在对象/库文件中的本机代码,然后仅使用标头中可用的原型进行引用,这仅仅是因为这种函数只有一个版本。
使用模板,C++ 编译器必须分别编译函数的每个实例。显然,它不能“提前”完成,因为您可以为其实例化函数的类型集实际上是无限的(您始终可以在调用函数之前在代码中定义新类型)。事实上,同一个函数模板的两个实例化可以完全不同。考虑这种极端情况:
struct t1 {
template <int>
struct a {};
};
struct t2 {
enum { a = 123 };
};
enum { b = 456, c = 789 };
template <class T>
void foo() {
T::a<b>c;
}
现在如果我们调用foo<t1>()
,它里面的语句是一个局部变量声明,因为t1::a
是一个类模板:
T::a<b> c;
但是如果我们调用foo<t2>()
,里面的语句是一个表达式,因为t2::a
是一个整数常量:
(T::a < b) > c;
这只是为了表明编译器无法有意义地“编译”模板;它确实必须主要保留令牌。
综上所述,ISO C++ 确实提供了分离模板的声明和定义的能力,因此它们可以被视为普通函数,在 .h 文件中声明,在 .cpp 文件中定义。这称为“导出模板”,因为您必须在声明和定义之前加上关键字export
:
// link.h
export template <class T>
T *Link(T *&, T *(*)());
// link.cpp
export template <class T>
T *Link(T *&ChildNodeReference, T *(*ObjectCreator)()) {
}
然而,这是标准的一个有争议的特性,因为实施的负担非常高,并且大多数流行的实施拒绝实施它;值得注意的是,g++、MSVC 和 C++Builder 没有实现它。我所知道的唯一支持它的编译器是Comeau C++。