テンプレートクラスの「特殊化」と「明示的インスタンス生成」を同時に使う場合は注意が必要だ(2)

先日の記事で扱った問題の対処法について.結論:テンプレートクラスの(部分)特殊化を行う場合は,そのテンプレートクラスを宣言しているヘッダファイルで「(部分)特殊化の宣言」を行うこと.部分特殊化の実装は,通常のクラスまたはメンバ関数の実装と同様にすること(inline 関数やテンプレート関数ならその場で実装,そうでないならユニットの実装ファイルに実装を記述し,実装が複数のオブジェクトファイルで重複しないようにする).
先日の記事の例の場合, unit1.h を以下のように変更する:

/*! \file    unit1.h
    \date    Feb.19, 2009
*/
#ifndef unit1_h
#define unit1_h
#include <iostream>
namespace hogehoge
{

template <typename T>
struct TTest
{
  T x;
  void print (void) const;
};

// 追加:
// declaration of specialization (特殊化の宣言)
template <>
void TTest<int>::print (void) const;

}  // end of namespace hogehoge
#endif // unit1_h

ここで追加された部分特殊化の宣言により,このヘッダを include するソースでは TTest<T>::print が T=int に対して部分特殊化されていることを知ることができる.この特殊化された関数は unit1.o でしか定義されていないから,リンカは確実に unit1.o をリンクするようになり,先日の記事のような問題は生じない.

なお, unit1.h に TTest<int>::print の実装を書くと,複数のオブジェクトファイルで TTest<int>::print が定義されることになり,リンカが重複定義エラーを吐くので注意されたい.もちろんこの関数が inline 関数であれば,ここに定義を書けばよい.

コンパイル

g++ -Wall unit1.cpp -c
ar r unit1.a unit1.o
g++ -Wall unit2.cpp -c
g++ -Wall main2.cpp -c
g++ -Wall unit2.o main2.o unit1.a

して実行すると:

x is hoge
x= 0xa
x is 2.5

のように,期待通りの結果が得られる.

何が問題か?

今回のケースでの問題は,部分特殊化の宣言をちゃんと書かなかったことが原因だ.しかも,コンパイラやリンカがそれを指摘してくれないことが,問題を深めている.実行ファイルはちゃんと生成され,しかも場合によっては期待通りに動き,ある特定の場合にしか期待を裏切る振舞をしないことが問題なのである.

テンプレートクラスの「特殊化」と「明示的インスタンス生成」を同時に使う場合は注意だ.というよりも,このような場合に限らず,普段から「特殊化」する場合には「特殊化の宣言」をテンプレートと同じヘッダ内に書くようにするべきだろう.