C++ 模板显式实例化

头文件:template.hpp

#ifndef TEMPLATE_HPP
#define TEMPLATE_HPP

template <typename T>
void println(T t);

#endif

源文件:main.cpp

#include "template.hpp"

int main() {
  println(9);
  println(-7);
}

源文件:instantiation.cpp

#include <iostream>

#include "template.hpp"

template <typename T>
void println(T t) {
  std::cout << t << "\n";
}

// 特化声明:失败,因为没有定义
// template <>
// void println<int>(int);

// 显式实例化:成功
template void println<int>(int);
// template void println(int); // 如果能够从函数参数中推导出模板参数,也可以省略模板参数

// 特化定义:成功
// template <>
// void println<int>(int t) {
//   std::cout << "instantiation.cpp: " << t << "\n";
// }

翻译单元 A 要调用模板函数:

  1. 如果它能看到模板函数的特化定义,就会直接使用。
  2. 如果它只能看到模板函数的定义(即便是看到了特化函数的声明也不行),但是看不到特化的定义,就会去生成。
  3. 如果只能看到模板函数的声明,则没办法生成,就只能等待链接。

所以想要节省编译时间只能在头文件中留下声明,不能留定义。

在实例化的时候也要注意,首先是对每一个类型都写一遍函数体太累了,可以直接在源文件中提供定义,然后显式实例化或者特化。两者关系如下:

  1. 显式实例化和特化不能一起出现。显式实例化在特化之前则会报错,在特化之后会没有效果(相当于没有使用显式实例化)。
  2. 写法上有区别:注意特化是要加 template <> 的,而显式实例化是加的 template,没有 <>
  3. 显式实例化不能提供定义(函数体),特化可以有定义也可以没有(在和显式实例化进行对比的这种场景下,我们希望特化有定义,否则函数无法生成,链接不能成功)。

Warning

特化函数只声明不定义没有意义。如果所在翻译单元能看到 primary 模板的定义,则会自己生成,如果看不到则会延迟到链接阶段去解决符号查找问题,是否有特化函数声明没有影响。

为什么需要显式实例化?因为模板实例化是按需进行的,显式实例化就是告诉编译器这个函数之后会被用到,所以先实例化出来方便链接。