template class の static メンバの件

研究室のツールを gcc 4 だとコンパイルできないという問題があって、おかげで演習3が進まない。これは悪いことをした。それはそれとして、このままなのはマズイので、研究室で先輩と一緒にデバッグ。というか、コンパイルはできるんだがリンクで失敗するんだよね。すごく久し振りに、C++ の、しかもマニアックな部分をいじくる。


問題となる部分は、template クラスにおける static メンバの実体がないと怒られる。

template <typename T>
class C {
public:
  static string s;
};

template<> string C<int> s;
template<> string C<double> s;

int main()
{
  C<int>::s = "int";
  C<double>::s = "double";
}

インスタンス化させた template クラス分、実体を作っておかないとマズイ。と思うわけだ。少なくとも、 gcc 3.4.x ではこれで通るのだ。ちなみにあとからわかったことだが、上記のコードは gcc 4.0.x でも、何事もなかったようにコンパイルに通った。問題のアプリケーションは通らないのだが。実はこれが問題の本質じゃなかったのかもしれない。
それはさておき、実は以下のようなコードを書いても大丈夫だということが発覚した。

template <typename T>
class C {
...
};

template <typename T> string C<T> s;

...

少し私にとって衝撃的である。s の実体がいくつ必要かわからないのに、こんなんでいいのか。こんなんでいいって誰が言ったのか、というかどう書けばいいのか誰も教えてくれない。それが C++。あー、こんな書き方あったのかよと知った日。そんな日。

s の実体がいくつ必要か(つまり、C::s, C::s だけでいいのか?)は同一オブジェクトファイル内で使用される s に依る。つまり、分割コンパイルした先で C::s を使おうとしても、実体がないのでエラーとなるわけだ。もちろん、そのソース内で C::s を宣言すればコンパイルできるだろうが、別の問題が発生することは分かるだろう(今度は複数の実体を持つかもしれない)。これは困った。せっかく template まで持ち出したというのに、分割コンパイルできないとは。

と思ったが、そこまで問題でないことが直ちに分かった。つまり、分割された両方のファイルで、template string C s; がかかれていれば問題ない。例えば、foo.cc と var.cc にこの宣言が存在して、両方で例えば C::s を使ったとしても、実体が2つになることはないらしい。そのあたり、どうやってマージしているのかはよくわからないが、とにかくそのようだ。

ちなみに、このような記法は当初 gcc 4.0.x で追加されたと思われたが、実は gcc 3.4.x でも、gcc 3.2.x でも動くことが分かった。そんな・・・。なわけで、これが当該アプリケーションの本当の問題だったかどうかは、結局分からずじまいだったのだが、どういうわけかこの修正でコンパイルが通るようになった。ついでに、cl7 (VC)でも、cl8 でも余裕で通る。単に知らなかっただけなのか。Stroustrup 本の template の章をざっと見たが、該当する記述は見つからない。

というわけで、上記のようなプログラムの最善策は・・・。

template <typename T>
class C {
public:
  static string s;
};

template <typename T> string C<T>::s;

これを、各ソースファイルに include すればいいということだ。え、こんなんでいいの?というのがその場にいたみんなの感想だったので、まぁなんというか・・・。何度も言う。C++ はすでに破綻している。