(b) が存在しない場合、(c) は確かに (a) の有効な特殊化になります。実際、コンパイラが (b) を認識する前に (c) が現れるようにソース行の順序を変更するだけで、(a) の特殊化になります!
次のコードを検討してください。
int main()
{
int a;
f(&a);
return 0;
}
ここで、コンパイラの立場になってみましょう。f
引数を持つ一致する関数を見つける必要がありint*
ます。職業はなんですか?
- 最初に、既知のすべての非テンプレート関数を試して
f
、引数の型 (この場合は ) に一致する関数があるかどうかを確認しますint*
。
- 完全に一致するものが見つからない場合は、知っているすべての基本テンプレートを調べます。この場合、 と の 2 つが
f<T>
ありf<T*>
ます。クラスとは異なり、関数テンプレートは部分的に特殊化できないため、コンパイラに関する限り、これらは完全に別個のオーバーロードであることに注意してください。
- 明らかに
f<T*>
ベース テンプレートの方が適しているため、 でインスタンス化しますT=int
。の特殊化をすでに見た場合はf<int*>
、それを使用し、それ以外の場合は関数を生成します。
ここに面白いことがあります。元のコードの順序を変更すると、
template<class T> void f( T ); // (i)
template<> void f<>(int*); // (ii)
template<class T> void f( T* ); // (iii)
次に、コンパイラは (ii) を (i) の特殊化と見なします。これは、物事を順番に処理し、(ii) に到達した時点で (iii) がまだ存在することを認識していないためです。しかし、これはベース テンプレートでのみ一致するため、(iii) は (i) よりも適切であると判断し、現在 (iii) には特殊化がないため、デフォルトのインスタンス化が取得されます。
これらはすべて非常にややこしく、経験豊富な C++ プログラマーでさえつまずくことがあります。したがって、基本的なルールは次のとおりです。関数テンプレートを特殊化せず、代わりに通常の関数のオーバーロードを使用します。通常の古い非テンプレート
void f(int*);
何よりも先に一致し、この混乱全体を回避します。
編集: nm は、コメントで標準への参照を要求しました。残念ながら手元にあるのは C++03 バージョンだけですが、次のようになります。
パラグラフ 4.7.3.3: 「明示的に特殊化されている関数テンプレートまたはクラス テンプレートの宣言は、明示的な特殊化の宣言の時点でスコープ内にあるものとします。」.
そのため、上記の例では、(iii) がまだスコープに含まれていないため、(ii) を (iii) の明示的な特殊化と見なすことはできません。
セクション 4.8.3: 「その [関数] 名への呼び出しが書き込まれると... テンプレート引数の推定 (14.8.2) と明示的なテンプレート引数のチェック (14.3) が各関数テンプレートに対して実行され、テンプレート引数の値が検出されます。 (存在する場合) その関数テンプレートで使用して、呼び出し引数で呼び出すことができる関数テンプレートの特殊化をインスタンス化できます。"
言い換えれば、(私が読んだ限りでは) どのような場合でも、常に各基本テンプレートを調べます。明示的な特殊化を提供しても違いはありません。
同じ段落が続きます: 「各関数テンプレートについて、引数の推定とチェックが成功した場合、テンプレート引数 (推定および/または明示的) を使用して、単一の関数テンプレートの特殊化をインスタンス化します。これは、候補関数セットに追加されます。オーバーロードの解決に使用されます。」
したがって、明示的な特殊化が考慮されるのは、この時点 (私が読んだとき) だけです。
最後に、おそらくこの場合最も重要なのは、セクション 13.3.3 で、オーバーロード セット内の「実行可能な最適な関数」を選択することです。関連する項目は次の 2 つです。
ふぅ!