テンプレート関数の明示的インスタンス生成

通常,C++のテンプレート関数(orクラス)をライブラリ化するとき,それを利用するソースから,そのテンプレート関数の実装(定義)も含めてインクルードしなければならない.そうしないと,特定の型に対してテンプレート関数のインスタンスを生成できないからだ.しかし,この仕様には

  • ヘッダファイルの肥大化(コンパイル速度の低下)
  • 望ましくない実装の公開

などの問題がある.これを回避するために,特定の型に対してテンプレートのインスタンスを明示的に生成する explicit instantiation という方法がある.
explicit instantiation はテンプレートの特殊化 (explicit specialization) とは別物なので注意しなければならない.後者は,特定の型に対して特定の実装を与えるためのものだ.

さて,引数の2乗を返すテンプレート関数

template <typename T>
T square (const T &x)
{
  return x*x;
}

を題材にする.この関数をライブラリ化する場合,ヘッダファイル head.h にこの関数定義を書いておけばよい.しかしそれだと,この関数の中身が複雑になった場合に上記のような問題が出てくるので,ヘッダファイルには関数宣言

// head.h:
template <typename T>
T square (const T &x);

だけ書いておくことにする.この場合, head.h をインクルードしたソースで square(2.0) のように square を利用すると,そのソースのコンパイルは可能だが,リンカが

undefined reference to `double loco_rabbits::square<double>(double const&)'

のようなエラーを返すことになる(これは g++-4.3 の例).実装が与えられていないから当然だ.そこで square の実装を head.cpp に書いて,

// head.cpp:
#include "head.h"
template <typename T>
T square (const T &x)
{
  return x*x;
}

これをコンパイルして生成したオブジェクトファイル head.o をリンクすることにしてみる.しかし,これでもリンカが同じエラーを吐く.これは,上の square の実装は, double などの特定の型に対するインスタンスを与えるものではないからだ.特定の型に対するインスタンスを明示的に生成するには,

template double square<double> (const double&);

のような宣言を書く.これを explicit instantiation と呼ぶ.

つまり, head.cpp を

// head.cpp:
#include "head.h"
template <typename T>
T square (const T &x)
{
  return x*x;
}
template int square<int> (const int&);
template float square<float> (const float&);
template double square<double> (const double&);
template long double square<long double> (const long double&);

のように変更してコンパイル,オブジェクトファイル head.o を生成し, square を利用するソースからは head.h のみを include して,実行ファイル生成時に head.o をリンクすればいい.

蛇足だが,上の square のようなテンプレート関数の場合は,

template <typename T>
inline T square (const T &x)
{
  return x*x;
}

のようにインライン化してしまった方がベター.

参考文献