質問: 今更気がついたのですが、C++の関数テンプレートを明示的特殊化した関数と、同じ名前の非テンプレートとしてオーバーロードした関数って、ほとんど同じものと思います。 微細な差はありますが、大差ないのでどちらか一方でも十分な気がしています。 ``` #include // プライマリー・テンプレート template void f(T) { std::cout << "f(T)\n"; } // 明示的特殊化 template<> void f(int) { std::cout << "F(int)\n"; } // オーバーロード void f(int) { std::cout << "f(int)\n"; } int main() { f(1.1); // f(T) f(1); // f(int) } ``` Bjarne Stroustrup著「C++の設計と進化」の476ページによると > > そこで私は、テンプレートを”特殊化”する仕組みが必要だと結論した。それは、テンプレートが一般的なオーバロードの仕組みを受け入れることによって、または、もっと特定の仕組みによって実現するだろう。私は特定の仕組みの方を選んだ。主な問題はCの不規則性だと思っていたのと、オーバロードを提案すると常に抗議の声の大合唱がわき起こるからだ。 > > > この本は、この後「特定の仕組み」として「明示的特殊化」と思われる方法を解説しています。(C++当初の文法なので現在とはちょっと違う書き方になってますが、意味は「明示的特殊化」と思います。) しかし、現在はオーバロードと明示的特殊化の両方がサポートされています。 もしかして、何か大きな使い勝手の差が両者にはあるのでしょうか? そうではなく、C++の当初は明示的特殊化だけだったのが、やっぱりオーバ-ロードの方が便利ということでオーバーロードが導入され、互換性維持のため明示的特殊化が残されたということでしょうか? はたまた他の理由でしょうか? もし、ご存じの方がいらっしゃいましたら、是非教えて頂けると嬉しいです。 また、ご存知無い方も「私はこう推測する」等のご意見を貰えると有り難いです。
回答: > > C++の関数テンプレートを明示的特殊化した関数と、同じ名前の非テンプレートとしてオーバーロードした関数って、ほとんど同じものと思います。 > > > 両者は明確に異なるものと解釈すべきかもしれません。下記説明ではいずれも「基本的には(非テンプレート)関数オーバーロードの利用」を推奨しています。 StackOverflow回答 Template Specialization VS Function Overloading: > > Short story: overload when you can, specialise when you need to. > > [...] > > > Herb Sutter氏記事 "Why Not Specialize Function Templates?" > > Summary > > [...] For another thing, function template *specializations don't overload.* This means that any specializations you write will not affect which template gets used, which runs counter to what most people would intuitively expect. After all, if you had written a nontemplate function with the identical signature instead of a function template specialization, the nontemplate function would always be selected because it's always considered to be a better match than a template. [...] > > > 記事中で言及されているコード例: ``` template // (a) (プライマリ)テンプレート void f( T ) { std::puts("a"); } template // (b) 別のプライマリテンプレート, (a)のオーバーロード void f( T* ) { std::puts("b"); } template<> // (c) テンプレート(b)の完全特殊化 void f<>(int*) { std::puts("c"); } int *p = nullptr; f( p ); // "c"を出力 ``` b, c の定義順を入れ替えると... ``` template // (a) (プライマリ)テンプレート void f( T ) { std::puts("a"); } template<> // (c) テンプレート(a)の完全特殊化 void f<>(int*) { std::puts("c"); } template // (b) 別のプライマリテンプレート, (a)のオーバーロード void f( T* ) { std::puts("b"); } int *p = nullptr; f( p ); // "b"を出力 ``` 詳細は記事に譲りますが、関数の解決プロセスではテンプレート関数の完全特殊化は無視され、(プライマリテンプレートの)関数オーバーロードのみが考慮されるため、このような結果となるようです。