1. 编译的两个步骤

模板在编译时需要经过两个步骤:

  1. 在模板函数的定义时期,此时模板还未被某个类型实例化(instantiation),此时会经历以下检查:
    1. 语法检查,例如是否少了某些符号
    2. 是否出现了不依赖template parameter的未命名的符号(例如未声明的类型,函数名等)
    3. 不依赖template parameter的static assert 检查
  2. 在实例化阶段,模板的代码会被再次针对该类型进行一些检查,是否符合语法规范等。

例如,以下代码:

template<typename T>
void foo(T t) {
  funcA(); // 如果funcA()未定义,则在第一阶段检查出错
  funcA(t); // 如果funcA(T)未定义,则在第二阶段检查出错
  static_assert(sizeof(T) > 10, // 第一阶段检查出错
               "int too small");
  static_assert(sizeof(T) > 10, // 如果T的size小于10 
               "T too small"); // 则在第二阶段检查出错
}

正因为两阶段编译的特性,如果像往常的方式实现模板的声明和定义(声明写在头文件中,定义写在cpp文件中),会导致程序编译时没问题,但是在link时出现 no definition of xxx的报错。

举个例子:

模板函数声明:

// myfirst.hpp
#pragma once

// declaration of template
template <typename T>
void printTypeOf (T const&);

模板函数定义

// myfirst.cpp
#pragma once
#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"

// implementation/definition of template
template <typenaem T>
void printTypeOf(T const& x){
  std::cout << typeid(x).name() << "\n";
}

使用模板函数

// main.cpp
#include "myfirst.hpp"

int main(){
  double x = 3.0;
  printTypeOf(ice); // call function template of double
}

这个时候,编译时一切正常,但是在link的时候,会报错printTypeOf()函数没有定义。具体原因是printTypeOf函数没有被实例化,因为这个函数在实例化double类型的时候,只看到了函数的声明,没有看到函数的定义。

针对这个问题,具体的解决办法很简单,把模板函数的声明和实现都放在头文件中

// myfirst2.hpp
#pragma once
#include <iostream>
#include <typeinfo>

// declaration
template <typename T>
void printTypeOf(T const&);

// impl
template<typename T>
void printTypeOf(T const& x) {
  std::cout << typeid(x).name() << "\n";
}

####

Reference

[1] C++ Templates The Complete Guide Second Edition, David Vandevoorde Nicolai M. Josuttis Douglas Gregor