明示的な特殊化を関数宣言と考える必要があります。2つのオーバーロードされた関数(テンプレート化されていない)がある場合と同様に、2番目のバージョンを呼び出そうとする前に、宣言が1つしか見つからない場合、コンパイラーは、必要なオーバーロードされたバージョンが見つからないと言います。テンプレートとの違いは、コンパイラが一般的な関数テンプレートに基づいてその特殊化を生成できることです。では、なぜこれを行うことが禁止されているのですか?完全なテンプレートの特殊化は、見たときにODRに違反しているため、その時点で、同じタイプのテンプレートの特殊化がすでに存在しているためです。テンプレートがインスタンス化されると(暗黙的かどうかに関係なく)、対応するテンプレートの特殊化も作成されます。後で(同じ翻訳単位で)同じスペシャライゼーションを使用すると、インスタンス化を再利用でき、インスタンス化ごとにテンプレートコードを複製する必要がなくなります。明らかに、ODRは、他の場所に適用されるのと同じように、テンプレートの特殊化にも適用されます。
したがって、引用されたテキストに「診断は不要」と記載されている場合、問題は明示的な特殊化の前に発生するテンプレートのインスタンス化が原因であるという洞察に満ちたコメントをコンパイラが提供する必要がないことを意味します。しかし、それが行われない場合、他のオプションは、標準のODR違反エラー、つまり「[T=int]の「Foo」特殊化の複数の定義」などを与えることです。より賢い診断として役立ちます。
編集への応答
1)ことわざには、すべてのテンプレート関数定義(つまり実装)がインスタンス化の時点で表示されている必要があります(コンパイラーがテンプレート引数を置き換えて関数テンプレートをインスタンス化できるようにするため)。ただし、関数テンプレートの暗黙的なインスタンス化には、関数の宣言が使用可能である必要があるだけです。したがって、あなたの場合、それを2つの変換単位に分割することは機能します。これは、ODRに違反しないため(そのTUではの宣言が1つしかないため)、暗黙のインスタンス化ポイントで使用可能なFoo<int>
場合の宣言(から)、およびの定義Foo<int>
Foo<T>
Foo<int>
TU B内のリンカが利用できます。したがって、この2番目の例が「機能しない」と主張する人は誰もいません。これは、想定どおりに機能します。あなたの質問は、1つの翻訳単位内に適用されるルールについてです。2つのTUに分割した場合(特に、2つのTUで明らかに正常に機能するはずの場合)、エラーは発生しないと言って、議論に反論しないでください。ルール)。
2)最初の例では、コンパイラが一般関数テンプレート(特殊化されていない実装)を見つけることができず、したがってFoo<int>
一般テンプレートからインスタンス化できないため、エラーが発生します。または、コンパイラーは一般的なテンプレートの定義を見つけ、それを使用してインスタンス化しFoo<int>
、2番目のテンプレートの特殊化Foo<int>
が検出されたためにエラーをスローします。あなたは、コンパイラがあなたの専門分野に到達する前にそれを見つけるだろうと思っているようですが、そうではありません。C ++コンパイラは、コードを上から下にコンパイルします。あちこちで何かを置き換えるために行ったり来たりすることはありません。コンパイラがの最初の使用にFoo<int>
到達すると、その時点で一般的なテンプレートのみが表示され、インスタンス化に使用できるその一般的なテンプレートの実装があると想定されます。Foo<int>
、それはのための特別な実装を期待していません、それFoo<int>
は一般的なものを期待して使用します。次に、特殊化を確認してエラーをスローします。これは、一般バージョンを使用することをすでに認識しているため、同じ関数に対して2つの異なる定義が表示され、ODRに違反しているためです。それはそれと同じくらい簡単です。
なぜああなぜ!!!
2 TUの場合は、C ++の機能であるTU間でテンプレートのインスタンス化を共有できる必要があるため、機能する必要があります。インスタンス化の可能性が少ない場合は、それらをプリコンパイルできます。
C ++で何かを宣言すると、コンパイラに「このことはどこかに定義されている」と通知されるため、 1TUの場合は許可されません。コンパイラに「テンプレートの一般的な定義がどこかにある」と言い、次に「一般的な定義を使用して関数を作成したい」とFoo<int>
言い、最後に「呼び出されるたびFoo<int>
にこの特別な定義を使用する必要がある」と言います。それは完全な矛盾です!だからですODRは存在し、このコンテキストに適用され、そのような矛盾を禁止します。「検出される」という一般的な定義が存在しないかどうかは関係ありません。コンパイラーはそれを予期し、存在し、特殊化とは異なると想定する必要があります。先に進んで「わかりました。コード内の他の場所で一般的な定義を探します。見つからない場合は、戻って、この特殊化を一般的なものの代わりに使用することを「承認」します。定義ですが、それが見つかった場合は、この特殊化にエラーのフラグを付けます。」また、後で表示される特殊化を使用するコードに対して、一般的なテンプレートを使用する意図を明確に示すコードを変更することによって、プログラマーの要望を完全に無視することもできません(特殊化はまだ宣言されていないため)。私は出来ます'
2TUの場合は完全に異なります。コンパイラがTUA(を使用する)をコンパイルしているときFoo<int>
、一般的な定義を探し、それを見つけることができず、後でとしてリンクインされると想定しFoo<int>
、シンボルプレースホルダーを残します。次に、リンカはテンプレートを検索しないため(実際には、テンプレートはエクスポートできません)、を実装する関数を検索し、Foo<int>
それが特殊なバージョンであるかどうかは関係ありません。リンクする同じシンボルが見つかった場合、リンカーは問題ありません。これは、プログラマー(コンパイルされたライブラリーの関数を簡単に変更できない)とコンパイラーベンダー(実装する必要がある)の両方にとって、そうでなければ(「エクスポートされたテンプレート」に関する議論を調べる)悪夢になるためです。このリンククレイジースキーム)。