テンプレート使用時の分割コンパイルの方法
汎用的な関数を定義したいときに、複数の型で使えるようにコードを大量にコピペするのは、愚の骨頂なので、C++にはテンプレート関数と言う機能が備わっています。
そのテンプレート関数(おそらくクラスでも同じ問題が出るであろう)を分割コンパイルするときの問題について、id:tkuroに教えてもらったので、やり方をまとめておく。
やりたいこと
下記のような平方を計算するルーチンを汎用テンプレート関数として登録し、他のファイルから使いまわす、と言うもの。
何も考えずに書くと、下記のようなファイル構成になると思います。
- square.cpp
template <typename T> T square(T x) { return x * x; }
- square.h
template <typename T> T square(T);
- main.cpp
#include <iostream> #include "square.h" int main(int argc, char** argv) { std::cout << square<int>(2) << std::endl; std::cout << square<float>(2.5) << std::endl; }
これで、下記の通りコンパイルすると、
% g++ -O2 -c square.cpp % g++ -O2 -c main.cpp % g++ -O2 -o main square.o main.o Undefined symbols: "int square(int)", referenced from: _main in main.o "float square (float)", referenced from: _main in main.o ld: symbol(s) not found collect2: ld returned 1 exit status
と、リンクのところで、int square(int), float square(float)がシンボルテーブルに見つからんよ、と怒られます。
素人な私は、(゚Д゚≡゚Д゚)エッナニナニ? テンプレート使ってるんだから、当たり前じゃん!!と思ったのですが、gccはどうやらそういう仕組らしい。
分割コンパイルする方法
ここで、id:tkuro 氏に相談したところ、square.cppの中でdummy関数を作っておいて、squareの実体を作っておけばよい、と言うアドバイスを受けました。
すなわち、
- square.cpp
#include "square.h" template <typename T> T square(T x) { return x * x; } static void dummy() { square<int>(0); square<float>(0.0); }
と言うように、dummy関数を置いてやって、その中で実体を生成してやる。
さらに、最適化オプション使用時に
- dummy() が勝手にパージされる
- square()が勝手にインライン化される
のを防ぐために、square.hを下記のようにすべし、とのアドバイス。
- square.h
// g++使用時 template <typename T> T square(T) __attribute__ ((noinline)); static void dummy() __attribute__ ((used));
これで、テンプレート使用してても、めでたく分割コンパイルが出来るようになりました。
id:tkuro ありがとうございました。