テンプレート使用時の分割コンパイルの方法

汎用的な関数を定義したいときに、複数の型で使えるようにコードを大量にコピペするのは、愚の骨頂なので、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関数を置いてやって、その中で実体を生成してやる。

さらに、最適化オプション使用時に

  1. dummy() が勝手にパージされる
  2. square()が勝手にインライン化される

のを防ぐために、square.hを下記のようにすべし、とのアドバイス。

  • square.h
// g++使用時
template <typename T> T square(T) __attribute__ ((noinline));
static void dummy()  __attribute__ ((used));

これで、テンプレート使用してても、めでたく分割コンパイルが出来るようになりました。
id:tkuro ありがとうございました。