前提
まだ完全にはわからない
Tが汎用の場合
std::vector<T>などのTに何でも入れられることを前提にしてつくられているクラスはヘッダに書くしかないと思う。
Tが一部の型のみの場合
この場合はソースファイルにかける。
ここでは文字列の長さをを返す関数でchar*とwchar_t*をとるGetStringLength()を考える。
ヘッダ
1 2 3 4 5 6 7 8 9 10 |
template<typename T> size_t GetStringLength(const T* p) { // should never come here static_assert(sizeof(T) == 0, "only char* or wchar_t*"); } template<> size_t GetStringLength<char>(const char* p); template<> size_t GetStringLength<wchar_t>(const wchar_t* p); extern template size_t GetStringLength<char>(const char* p); extern template size_t GetStringLength<wchar_t>(const wchar_t* p); |
最初のテンプレート関数は汎用のTを受け取る。もしテンプレート関数が実体化されたときはstatic_assertでエラーになるようにしている(sizeof(T)は決して0にならない。falseを指定してしまうと実体化しなくてもエラーになってしまう(コンパイラ依存))。char*とwchar_t*で特殊化するので、それ以外のポインタで呼ばれたときに実体化される。
次の2つは特殊化の宣言。定義はソースに書く。
次の2つは特殊化が実体化されるときの場所の指定。externでどこかにそれがあることを示している。
ソース
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include "header.h" #include <string.h> template<> size_t GetStringLength<char>(const char* p) { return strlen(p); } template<> size_t GetStringLength<wchar_t>(const wchar_t* p) { return wcslen(p); } template size_t GetStringLength<char>(const char* p); template size_t GetStringLength<wchar_t>(const wchar_t* p); |
最初の2つは特殊化の定義。
次の2つは特殊化が定義されるときここに実体化されることを意味する。
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include "header.h" int main() { const char* p = "aaa"; const wchar_t* pw = L"bbb"; std::cout << "Length of p is " << GetStringLength(p) << '\n'; std::cout << "Length of pw is " << GetStringLength(pw) << '\n'; int* pi; // GetStringLength(pi); // static_assert } |
今回の場合は実装がほぼないのでテンプレートにする意味があんまりないが、2つの実装がほぼ同じで、一部だけ書き換えたいときは汎用Tの方に実装を書くこともできる。その場合にこのテンプレートを使うこともできる。
Tが一部の型のみの場合(バージョン2)
ヘッダー
1 2 3 4 |
template<typename T> size_t GetStringLength(const T* p); extern template size_t GetStringLength<char>(const char* p); extern template size_t GetStringLength<wchar_t>(const wchar_t* p); |
ソース&main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "header.h" #include <string.h> template<typename T> size_t GetStringLength(const T* p) { if (p == nullptr) return 0; size_t size = 0; for (; *p; ++p) ++size; return size; } template size_t GetStringLength<char>(const char* p); template size_t GetStringLength<wchar_t>(const wchar_t* p); |
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include "header.h" int main() { const char* p = "aaa"; const wchar_t* pw = L"bbb"; std::cout << "Length of p is " << GetStringLength(p) << '\n'; std::cout << "Length of pw is " << GetStringLength(pw) << '\n'; int* pi = 0; // GetStringLength(pi); // link error } |
ソース:https://github.com/ambiesoft/blogprogs/tree/master/6038
考察
テンプレート関数の定義をソース・ファイルに書くと、他のソース・ファイルからは見ることができないのでリンカーでつなげることになる。よって鉤括弧なしのtemplateは実装を実体化し外部に公開して他のソース・ファイルから見えるようにしているのだと思われる。
鉤括弧ありのテンプレートはあくまでコンパイルフェーズで解決するものであり、宣言だけのテンプレートを実体化しても宣言だけになり、それがリンクされることもないのだと思われる。