5

これは、この質問の一種です。

#include <iostream>

struct type1 {};
struct type2 {};

void foo(type1 x)
{
  std::cout << "foo(type1)" << std::endl;
}

template<typename T>
void bar() {
  foo(T());
}

int main()
{
  bar<type1>();
  bar<type2>();
  return 0;
}

void foo(type2 x)
{
  std::cout << "foo(type2)" << std::endl;
}

上記のコードは、 infoo(type2)のインスタンス化時に表示されません。それでも、コードはコンパイルされ、次の出力が生成されます。bar<type2>main

foo(type1)
foo(type2)

foo(type2)コンパイラは、でインスタンス化するときに利用可能bar<type2>であることをどのように認識しますmainか?

編集:テンプレートのインスタンス化中の過負荷解決がどのように機能するかについてもっと理解しようとしています。以下のコードを検討してください。

#include <iostream>

struct type1 {};
struct type2 {};
struct type3 {
  operator type2() { return type2(); }
};

void foo(type1 x)
{
  std::cout << "foo(type1)" << std::endl;
}

void foo(type2 x)
{
  std::cout << "foo(type2)" << std::endl;
}

int main()
{
  foo(type3());
  return 0;
}

void foo(type3 x)
{
  std::cout << "foo(type3)" << std::endl;
}

出力は

foo(type2)

より厳密な一致が利用可能であっても、その時点までコンパイラによって解析された唯一の候補であったためfoo(type3)、呼び出しfoo(type3())は解決されます。foo(type2)ここで、次のコードについて考えてみます。

#include <iostream>

struct type1 {};
struct type2 {};
struct type3 {
  operator type2() { return type2(); }
};

void foo(type2 x)
{
  std::cout << "foo(type2)" << std::endl;
}

template<typename T>
void bar() {
  foo(T());
}

int main()
{
  bar<type3>();
  return 0;
}

void foo(type3 x)
{
  std::cout << "foo(type3)" << std::endl;
}

出力は

foo(type3)

つまり、呼び出しの時点で、表示されbar<type3>()ているだけであってもfoo(type2)、コンパイラfoo(type3)は、より近い一致であるため、後で来るものを選択します。

4

2 に答える 2

6

foo(type2)関数が別のファイルで提供されている可能性があるため、定義なしで残されたシンボルは、リンクプロセス中に置き換えられます。

コンパイラーは、それ以上の置換を適用できないときに、必要な関数がプロセス全体の終わりまでに定義されているかどうかを判断します。

理解を明確にするために、たとえば、一般的なCプログラムをコンパイルするために必要な手順を知っておく必要があります。

  • まず、コード上のすべてのマクロを展開します。

  • 次に、コードは言語構文に従って検証されるため、アセンブリ言語(コンパイルプロセス自体)に変換できます。このステップでは、定義なしで見つかったすべてのシンボルにエントリが付いたテーブルに注釈が付けられます。このエントリ(symbol, definition)は後で完成し、プログラムを適切に構築できるようにします。

  • 次に、アセンブリにコンパイルされたコードが機械語に変換されます。つまり、オブジェクトが作成されます。

  • 最後に、シンボル定義への依存関係を解決するために、すでに実行可能なオブジェクトをリンクする必要があります。この最後のステップでは、オブジェクトに未定義のシンボルがないかチェックし、他のモジュールまたはライブラリから定義を追加して、プログラムを完了します。

シンボルがその定義に正しく「リンク」されていない場合、コンパイラはプログラムのエラーを指摘します-クラシックundefined reference to...

投稿したコードを考慮すると、プロセスはコンパイラーに到達するまで実行されます。コンパイラはコードをトラバースし、、、、、およびの定義type1に注意します。type2foo(type1 x)bar<T>()

struct type1 {};
struct type2 {};

メインに到達すると、の呼び出しを見つけ、すでに既知であり、適切に使用できる、をbar<type1>();呼び出します。foo(type1())

void foo(type1 x) {
    std::cout << "foo(type1)" << std::endl;
}

template<typename T>
void bar() {
    foo(T());
}

int main() {

    bar<type1>();
    bar<type2>();
    return 0;

}

次の呼び出しに到達すると、、は呼び出しbar<type2>();を試みますfoo(type2())が、そのような定義は使用できません。したがって、この呼び出しは不明なシンボルとして関連付けられ、後のプロセスで定義に置き換える必要があります。

コンパイラがを実行した後、main新しい定義に到達します。これは、作成中の「変換テーブル」に定義がないものです。

void foo(type2 x) {
    std::cout << "foo(type2)" << std::endl;
}

したがって、次のステップでは、コンパイルによってシンボルをそれぞれの定義に置き換えることができ、プログラムは正しくコンパイルされます。

よろしく!

于 2012-11-21T01:50:13.690 に答える
3

答えは、引数依存の名前検索 (ADL) (リンクされた質問にも記載されています) を介して見つかります。foo(T());には 2 つのルックアップがあります。まず、テンプレートの定義時に、定義の時点で定義されたすべての関数がオーバーロード セットに含まれます。これは、コンパイラがfoo(T());の内部を見ると、オーバーロード セットにのみbar追加することを意味します。ただし、ADL と呼ばれる2 番目のルックアップが実行されます。テンプレートのインスタンス化時に、つまり、提供された引数 (この場合は ) と同じ名前空間で を探します。はグローバル名前空間にあるため、 void foo(type1 x)bar<type2>();footype2type2footype2グローバル名前空間でそれを見つけ、呼び出しを解決します。標準からの情報を探している場合は、 を参照してください14.6.4.2 Candidate functions

以下を試して、コードが失敗するのを確認してください。これはfoo、 と同じ名前空間で見つけることができないためa::type1です。

#include <iostream>

namespace a
{
  struct type1 {};
}

template<typename T>
void bar() {
  foo(T());
}

int main()
{
  bar<a::type1>();
  return 0;
}

void foo(a::type1 x)
{
  std::cout << "foo(a::type1)" << std::endl;
}
于 2012-11-21T03:22:55.937 に答える