Lightness Races in Orbitは、標準からの準拠部品ではない理由を挙げました。近くに他にもあるかもしれません。
標準の言い回しが何を意味するかをより簡単な言葉で説明しようとします。うまくいけば正しく理解でき、最後にリンカー エラー (またはエラーがないこと) を説明します。
- インスタンス化のポイントは何ですか?
- コンパイラはどのように特殊化を選択しますか?
- インスタンス化の時点で何が必要ですか?
- リンカ エラーの理由
1/ インスタンス化のポイントは何ですか?
テンプレート関数のインスタンス化のポイントは、それが呼び出されるか参照されるポイント ( &std::sort<Iterator>
) で、すべてのテンプレート パラメーターが肉付けされます (*)。
template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }
int main() { foo(1); } // point of instantiation of "foo<int>(int)"
ただし、他のテンプレートから呼び出されたテンプレートの場合、遅延する可能性があるため、正確な呼び出しサイトと一致しません。
template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }
template <typename T>
void bar(T t) { foo(t); } // not a point of instantiation, T is still "abstract"
int main() { foo(1); } // point of instantiation of "bar<int>(int)"
// and ALSO of "foo<int>(int)"
この遅延は、書き込みを可能にするため、非常に重要です。
- 共再帰テンプレート (つまり、相互に参照するテンプレート)
- ユーザー特化
(*) ざっくり言うと、テンプレートクラスの非テンプレートメソッドなどの例外があります...
2/ コンパイラはどのように特殊化を選択しますか?
インスタンス化の時点で、コンパイラは次のことができる必要があります。
- 呼び出す基本テンプレート関数を決定する
- そしておそらく、どの専門分野を呼び出すか
この古いGotWは専門化の難しさを誇示していますが、簡単に言えば:
template <typename T> void foo(T); // 1
template <typename T> void foo(T*); // 2
はオーバーロードであり、それぞれがベースとなる可能性のある特殊化の異なるファミリを生成します。
template <> void foo<int>(int);
は 1 の特殊化であり、
template <> void foo<int*>(int*);
2の専門です。
関数呼び出しを解決するために、コンパイラは最初に最適なオーバーロードを選択し、テンプレートの特殊化を無視します。次に、テンプレート関数を選択した場合は、より適切に適用できる特殊化があるかどうかを確認します。
3/ インスタンス化の時点で何が必要ですか?
したがって、コンパイラが呼び出しを解決する方法から、インスタンス化の最初のポイントの前に特殊化を宣言する必要があると標準が指定している理由を理解できます。それ以外の場合は、単に考慮されません。
したがって、インスタンス化の時点で、次のことをすでに確認している必要があります。
- 使用する基本テンプレート関数の宣言
- 選択する専門分野の宣言 (存在する場合)
しかし、定義はどうですか?
必要ありません。コンパイラは、TU で後で提供されるか、完全に別の TU によって提供されると想定します。
注: これは、コンパイラが遭遇したすべての暗黙的なインスタンス化を覚えておく必要があり、関数本体を発行できなかったことを意味するため、最終的に定義に遭遇したときに (最終的に) 必要なすべてのコードを発行できるようにするため、コンパイラに負担をかけます。それが遭遇したすべての特殊化のために。なぜこの特定のアプローチが選択されたのか、また、extern
宣言がなくても TU が未定義の関数本体で終了する可能性があるのはなぜだろうか。
4/ リンカ エラーの理由
定義が提供されていないため、gcc は後で提供することを信頼し、単に未解決のシンボルへの呼び出しを発行します。このシンボルを提供する別の TU とリンクした場合、すべて問題なく動作しますが、そうでない場合はリンカー エラーが発生します。
gcc はItanium ABIに従っているため、シンボルがどのように壊れているかを簡単に調べることができます。したがって、ABI はマングリングの特殊化と暗黙的なインスタンス化に違いがないことがわかります。
cls.f( asd );
呼び出し_ZN3cls1fIPKcEEvT_
( としてデマングルするvoid cls::f<char const*>(char const*)
) と特殊化:
template<>
void cls::f( const char* )
{
}
も生産してい_ZN3cls1fIPKcEEvT_
ます。
注: 明示的な特殊化に別のマングリングが与えられた可能性があるかどうかは、私には明らかではありません。