#1つのファイルを書いた場合
簡単な例(簡単のため参照は使用しない)
以下の関数funcをtemplateで書いてみよう.
#include <iostream>
using namespace std;
int func(int); //関数定義
int main(){
cout << func(2) << endl;
return 0;
}
int func(int a){ //実装
return a+a; //テキトーに足し算でもして返しましょう
}
templateで書いた.
#include <iostream>
using namespace std;
template <typename T>
T func(T); //templateで定義
int main(){
cout << func<int>(2) << endl;
return 0;
}
template <typename T> //実装
T func(T a){
return a+a;
}
これで,問題なく動きます.
ここでtemplateを用いた関数funcの定義,実装部分をmain.cpp, func.h, func.cppのように分けたい.
#分けた場合
#include <iostream>
#include "func.h"
using namespace std;
int main(){
cout << func<int>(2) << endl;
return 0;
}
#pragma once
template <typename T>
T func(T);
template <typename T>
T func(T a){
return a+a;
}
いつものように分割コンパイルすると...
エラー
#問題発生
エラー
In function `main':
main.cpp:(.text+0x15): undefined reference to `int func<int>(int)'
collect2: エラー: ld はステータス 1 で終了しました
make: *** [main] エラー 1
何故だろう?
g++の-cオプションでオブジェクトファイルを生成してみよう.
g++: 警告: func.o: リンクが完了しなかったのでリンカの入力ファイルは使われませんでした
リンカが上手くいっていないようだ.
いろいろ悩んで,twitterのフォロワー様にアドバイスを頂き,Google先生に訊いてみたところ,
分割コンパイルはあくまでもファイル単位でコンパイル(詳細は下の参考まで)
ということを理解すれば原因がわかる.
#エラーの仕組み
では,なぜエラーが出たのか見ていこう.
まず,main.cppのコンパイルを考える.
main.cppでfunc<int>(2)を呼び出しているので,ヘッダにある
template <typename T>
T func(T);
を見に行く.実装はコンパイラからは見えていないが,ヘッダにはちゃんと宣言されているのでひとまずOK.
(”C++はcppファイルごとにコンパイルするので他のcppファイルのことは知りません。なので教えてあげる必要があります。”@Drafear さんより)
次に,func.cppをコンパイルに移る.
template <typename T>
T func(T a){
return a+a;
}
templateで書かれているので,具体的な処理の実体化は行いようがない.
なので,特になんの具体的な処理も記述されていないオブジェクトファイルが生成される.
これで,すべてのファイルの分割コンパイルが終了.
リンク作業が始まります.mainのオブジェクトファイルがfunc<int>関数の実体を探しますがint型のfunc関数は存在しません.
なので,リンクが上手くいきませんでしたというエラーがでたということになる(らしい).
#解決法
・ヘッダに実装に全て書く.
・特定の型に対するインスタンスを明示的に書く
後者に関しては,func.cppを以下のように変更すれば良い.
template <typename T>
T func(T a){
return a+a;
}
template int func<int>(int); //追加
これでコンパイルが通る.
参考
pknight様 テンプレートの実装をヘッダに書かなければならない理由
aki-yam様 テンプレート関数の明示的インスタンス生成