このテンプレート関数が期待どおりに動作しないのはなぜですか?


23

私はテンプレート関数について読んでいて、この問題で混乱しました:

#include <iostream>

void f(int) {
    std::cout << "f(int)\n";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << "  ";
    f(val);
}

void f(double) {
    std::cout << "f(double)\n";
}

template void g<double>(double);

int main() {
    f(1.0); // f(double)
    f(1);   // f(int)
    g(1.0); // d  f(int), this is surprising
    g(1);   // i  f(int)
}

書いていない場合でも結果は同じですtemplate void g<double>(double);

私はg<double>後でインスタンス化する必要があると思うf(double)ので、fin の呼び出しはgを呼び出す必要がありますf(double)。驚くべきことに、それはまだ呼び出すf(int)g<double>。誰かがこれを理解するのを手伝ってくれる?


答えを読んだ後、私は私の混乱が本当に何であるかを理解しました。

これが更新された例です。それは私がのための専門を追加したことを除いて、ほとんど変わっていませんg<double>

#include <iostream>

void f(int){cout << "f(int)" << endl;}

template<typename T>
void g(T val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

void f(double){cout << "f(double)" << endl;}

//Now use user specialization to replace
//template void g<double>(double);

template<>
void g<double>(double val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

int main() {
    f(1.0); // f(double)
    f(1);  // f(int)
    g(1.0); // now d  f(double)
    g(1);  // i  f(int)
}

ユーザーの専門化により、g(1.0)期待どおりに動作します。

コンパイラーはg<double>、同じ場所で同じインスタンス化を自動的に行わないでください(またはmain()C ++プログラミング言語、第4版のセクション26.3.3で説明されているように、その後でも)。


3
最後の呼び出しは、g(1)与えるi f(int)私のため。あなたが書いたd f(double)。これはタイプミスでしたか?
HTNW

はい。ごめんなさい。更新
Zhongqi Cheng

テンプレートの基本的な原則は、ユーザーが宣言したシンボルによる内部ライブラリ呼び出しのハイジャックを防ぎながら、ユーザータイプの操作の使用をサポートすることです。テンプレートの「概念」契約がないため、これは不可能な妥協であり、そのような健全な「契約」を導入するには遅すぎます。
curiousguy

回答:


12

名前fは従属名であり(T引数によって異なりますval)、2つのステップに解決されます

  1. 非ADLルックアップは、テンプレート定義コンテキストから可視の関数宣言...を調べます
  2. ADLは、テンプレート宣言コンテキストまたはテンプレートインスタンス化コンテキストのいずれからも見える関数宣言を調べます

void f(double)、テンプレート定義のコンテキストから表示されていない、とADLはどちらかそれを見つけることができませんので、

基本型の引数の場合、関連する名前空間とクラスのセットは空です


あなたの例を少し修正することができます:

struct Int {};
struct Double : Int {};

void f(Int) { 
    std::cout << "f(Int)";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << ' ';
    f(val);
    // (f)(val);
}

void f(Double) { 
    std::cout << "f(Double)";
}

int main() {
    g(Double{});
}

これで、ADLはvoid f(Double)2番目のステップで検出され、出力はになります6Double f(Double)。ADLを無効にするには、の代わりに(f)(val)(または::f(val))を記述しf(val)ます。すると、出力は6Double f(Int)、例と一致してになります。


どうもありがとうございました。g <double>のインスタンス化がコードのどこにあるのかと思います。main()の直前ですか。もしそうなら、インスタンス化されたg <double>定義はf(int)とf(double)の両方を見ることができ、最後にf(double)を選択できるべきではありませんか?
Zhongqi Cheng

@ZhongqiChengステップ1では、テンプレート定義コンテキストのみが考慮され、そのコンテキストからvoid f(double)は表示されません-このコンテキストは宣言の前に終了します。ステップ2では、ADLは何も検出しないため、テンプレートのインスタンス化コンテキストはここでは何の役割も果たしません。
Evg

@ZhongqiCheng、あなたの編集での後void f(double)に定義を導入したので、この関数はそこから可視です。現在f、依存名ではありません。f(val);の定義後に宣言されたものに対してより良い一致があった場合g<double>、それも見つかりません。「先読み」する唯一の方法は、ADL(または2フェーズルックアップを正しく実装していない古いコンパイラ)です。
平均

これがあなたの答えに対する私の理解です。関数テンプレート(g <int>およびg <double>)は、テンプレート定義の直後にインスタンス化されると想定する必要があります。したがって、f(double)は表示されません。これは正しいです。どうもありがとうございます。
Zhongqi Cheng

@ZhongqiCheng、直前にインスタンス化main()。彼らは表示されませんf(double)、ルックアップの位相1はすでに行われていない、それは全く発見した:インスタンス化が発生したとき、それは遅すぎるので、f(double)
平均

6

問題はf(double)、あなたがそれを呼び出す時点では宣言されていません。その宣言をの前に移動template gすると、呼び出されます。

編集:なぜ手動のインスタンス化を使用するのですか?

(関数テンプレートについてのみ説明します。類似の引数はクラステンプレートにも当てはまります。)主な用途は、コンパイル時間を短縮したり、ユーザーからテンプレートのコードを隠したりすることです。

C ++プログラムは、コンパイルとリンクという2つのステップでバイナリに組み込まれます。関数呼び出しのコンパイルを成功させるには、関数のヘッダーのみが必要です。リンクを成功させるには、コンパイルされた関数の本体を含むオブジェクトファイルが必要です。

これで、コンパイラーがテンプレート関数の呼び出しを検出したときの動作は、テンプレートの本体を知っているか、ヘッダーのみを知っているかによって異なります。ヘッダーのみが表示される場合は、関数がテンプレート化されていない場合と同じことを行います。リンカーの呼び出しに関する情報をオブジェクトファイルに書き込みます。ただし、テンプレートの本文も参照する場合は、別のことも実行します。本文の適切なインスタンスをインスタンス化し、この本文をコンパイルして、オブジェクトファイルにも挿入します。

複数のソースファイルがテンプレート関数の同じインスタンスを呼び出す場合、それらの各オブジェクトファイルには、関数のインスタンスのコンパイル済みバージョンが含まれます。(リンカーはこれを知っており、単一のコンパイル済み関数へのすべての呼び出しを解決するため、プログラム/ライブラリの最終バイナリには1つしかありません。)ただし、各ソースファイルをコンパイルするには、関数をインスタンス化し、コンパイルに時間がかかりました。

関数の本体が1つのオブジェクトファイル内にある場合、リンカはそれを行うだけで十分です。テンプレートをソースファイルに手動でインスタンス化することは、コンパイラに関数の本体を問題のソースファイルのオブジェクトファイルに挿入させる方法です。(それは関数が呼び出されたかのようですが、インスタンス化は関数呼び出しが無効になる場所に書き込まれます。)これが完了すると、関数を呼び出すすべてのファイルは、関数のヘッダーのみを知ってコンパイルできるため、各呼び出しで関数の本体をインスタンス化してコンパイルするのにかかる時間を節約できます。

2番目の理由(実装の非表示)は、今では理にかなっているかもしれません。ライブラリ作成者がテンプレート関数のユーザーが関数を使用できるようにしたい場合は、通常、テンプレートのコードを提供して、自分でコンパイルできるようにします。テンプレートのソースコードを秘密にしたい場合は、ライブラリのビルドに使用するコードでテンプレートを手動でインスタンス化し、ソースの代わりに取得したオブジェクトバージョンをユーザーに提供できます。

これは意味がありますか?


著者の最初のコードで提示されたインスタンス化と、編集後の著者の2番目のコードでの特殊化の違いを説明できるとありがたいです。私は何度もcppreferenceの専門化とインスタンス化に関する本や本を読みましたが、理解できませんでした。ありがとう
Dev

@Dev:もう少し質問を指定してください。何に答えればいいのかわかりません。基本的にこの場合の違いは、特殊化が存在する場合はコンパイラーがそれを使用するのに対して、存在しない場合はコンパイラーがテンプレートを取得し、そのインスタンスを生成してこの生成されたインスタンスを使用することです。上記のコードでは、特殊化とテンプレートのインスタンスの両方が同じコードを導きます。
AshleyWilkes

私の質問は、コードのその部分に正確に隣接しています: "template void g <double>(double);" ご存知であれば、プログラミングテンプレートではインスタンス化と呼ばれます。スペシャライゼーションは少し異なります。それは、著者が送信した2番目のコードのように宣言されているためです。 val);} "違いを説明してもらえますか?
Dev

@Dev私はすでにそれをやろうとしました:可能であればコンパイラは特殊化を使用します。特殊化が表示されない場合(たとえば、特殊化がないため)、コンパイラーはテンプレートのインスタンスを作成し、そのインスタンスを使用します。上記のコードでは、テンプレートと特殊化の両方が同じ結果をもたらすため、唯一の違いは、コンパイラがその結果に到達するために行うことです。他の場合では、特殊化に実装を含めることができますが、テンプレートと共通するものはありません(メソッドヘッダーの場合)。より明確?
AshleyWilkes

1
これtemplate void g<double>(double);はいわゆる手動インスタンス化です(template山かっこなしのに注意してください。これは構文の際立った特徴です)。これは、メソッドのインスタンスを作成するようコンパイラーに指示します。ここではほとんど効果がありません。存在しない場合、コンパイラーはインスタンスが呼び出された場所にインスタンスを生成します。手動によるインスタンス化はめったに使用されない機能です。物事がより明確になったことを確認した後で、なぜそれを使用する必要があるのか​​を説明します
。-)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.