9

これが私が意味することです:

// test.h
class cls
{
public:
    template< typename T >
    void f( T t );
};

-

// test.cpp
template<>
void cls::f( const char* )
{
}

-

// main.cpp
int main()
{
    cls c;

    double x = .0;
    c.f( x ); // gives EXPECTED undefined reference (linker error)

    const char* asd = "ads";
    c.f( asd ); // works as expected, NO errors

    return 0;
}

これで全然大丈夫ですよね?

私はこれを疑うようにspecialization of '...' after instantiationなりました。これは、私にとって初めてのエラーに遭遇したからです。それで、私はこのエラーを「回避」しましたが、今はすべて正常に動作しているように見えますが、それでも..

これは明確に定義された動作ですか?


編集:非メンバー テンプレート関数 (前方宣言された非メンバー テンプレート関数) についても同じです。

4

3 に答える 3

6

Lightness Races in Orbitは、標準からの準拠部品ではない理由を挙げました。近くに他にもあるかもしれません。

標準の言い回しが何を意味するかをより簡単な言葉で説明しようとします。うまくいけば正しく理解でき、最後にリンカー エラー (またはエラーがないこと) を説明します。

  1. インスタンス化のポイントは何ですか?
  2. コンパイラはどのように特殊化を選択しますか?
  3. インスタンス化の時点で何が必要ですか?
  4. リンカ エラーの理由

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_ます。

注: 明示的な特殊化に別のマングリングが与えられた可能性があるかどうかは、私には明らかではありません。

于 2014-05-02T14:12:43.790 に答える
2

元のコードが正しくなく、「回避策」も標準に準拠していないと思います(コンパイラとリンカーが処理するにもかかわらず)。@Lightness Races in Orbitの回答で、標準からの適切な引用が引用されました。標準 ([temp.expl.spec] 14.7.3/6) からの次の例も参照してください。

class String { };
template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }

void f(Array<String>& v) {
  sort(v);          // use primary template
                    // sort(Array<T>&), T is String
}

template<> void sort<String>(Array<String>& v); // error: specialization
                                                // after use of primary template
template<> void sort<>(Array<char*>& v);        // OK: sort<char*> not yet used

実際には大きなコメントにすぎないため、回答をコミュニティ wiki としてマークしました。

于 2014-04-10T15:00:47.987 に答える