Ich habe über Vorlagenfunktionen gelesen und war durch dieses Problem verwirrt:
#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)
}
Die Ergebnisse sind die gleichen, wenn ich nicht schreibe template void g<double>(double);
.
Ich denke, g<double>
sollte danach instanziiert werden f(double)
, und daher sollte der Anruf bei f
in g
anrufen f(double)
. Überraschenderweise ruft es immer noch f(int)
an g<double>
. Kann mir jemand helfen, das zu verstehen?
Nachdem ich die Antworten gelesen hatte, fand ich heraus, was meine Verwirrung wirklich ist.
Hier ist ein aktualisiertes Beispiel. Es ist größtenteils unverändert, außer dass ich eine Spezialisierung hinzugefügt habe für 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)
}
Verhält sich mit der Benutzerspezialisierung g(1.0)
wie erwartet.
Sollte der Compiler diese Instanziierung nicht automatisch g<double>
an derselben Stelle main()
ausführen (oder sogar danach , wie in Abschnitt 26.3.3 der C ++ - Programmiersprache , 4. Ausgabe beschrieben)?
quelle
g(1)
gibti f(int)
für mich. Du hast geschriebend f(double)
. War das ein Tippfehler?Antworten:
Der Name
f
ist ein abhängiger Name (er hängtT
über das Argument abval
) und wird in zwei Schritten aufgelöst :void f(double)
ist im Kontext der Vorlagendefinition nicht sichtbar, und ADL findet es auch nicht, weilWir können Ihr Beispiel leicht ändern:
Jetzt wird ADL
void f(Double)
im zweiten Schritt finden und die Ausgabe wird sein6Double f(Double)
. Wir können ADL deaktivieren, indem wir statt(f)(val)
(oder::f(val)
) schreibenf(val)
. Dann wird die Ausgabe6Double f(Int)
in Übereinstimmung mit Ihrem Beispiel sein.quelle
void f(double)
ist nicht sichtbar - dieser Zusammenhang endet vor seiner Erklärung. In Schritt 2 ADL wird nichts finden, so dass die Vorlage Instanziierung Kontext hier keine Rolle spielen.void f(double)
, sodass diese Funktion von dort aus sichtbar ist. Jetztf
ist kein abhängiger Name. Wennf(val);
nach der Definition von eine bessere Übereinstimmung für deklariert wurdeg<double>
, wird diese ebenfalls nicht gefunden. Die einzige Möglichkeit, nach vorne zu schauen, ist ADL (oder ein alter Compiler, der die zweiphasige Suche nicht korrekt implementiert).main()
. Sie werden es nicht sehenf(double)
, denn wenn die Instanziierung stattfindet, ist es zu spät: Phase eins der Suche wurde bereits durchgeführt und es wurde keine gefundenf(double)
.Das Problem
f(double)
wurde an der Stelle, an der Sie es aufrufen, noch nicht deklariert. Wenn Sie die Deklaration vor das verschiebentemplate g
, wird sie aufgerufen.Bearbeiten: Warum sollte man manuelle Instanziierung verwenden?
(Ich werde nur über Funktionsvorlagen sprechen, analoge Argumentation gilt auch für Klassenvorlagen.) Die Hauptanwendung besteht darin, die Kompilierungszeiten zu verkürzen und / oder den Code der Vorlage vor Benutzern zu verbergen.
C ++ - Programme werden in zwei Schritten in Binärdateien integriert: Kompilieren und Verknüpfen. Damit die Kompilierung eines Funktionsaufrufs erfolgreich ist, wird nur der Header der Funktion benötigt. Damit die Verknüpfung erfolgreich ist, wird eine Objektdatei benötigt, die den kompilierten Hauptteil der Funktion enthält.
Wenn der Compiler nun einen Aufruf einer Vorlagenfunktion sieht, hängt seine Funktion davon ab, ob er den Hauptteil der Vorlage oder nur den Header kennt. Wenn nur der Header angezeigt wird, geschieht dasselbe wie wenn die Funktion nicht als Vorlage verwendet wurde: Informationen zum Aufruf des Linkers werden in die Objektdatei eingefügt. Wenn es aber auch den Hauptteil der Vorlage sieht, macht es noch etwas anderes: Es instanziiert die richtige Instanz des Hauptteils, kompiliert diesen Körper und fügt ihn ebenfalls in die Objektdatei ein.
Wenn mehrere Quelldateien dieselbe Instanz der Vorlagenfunktion aufrufen, enthält jede ihrer Objektdateien eine kompilierte Version der Instanz der Funktion. (Linker weiß davon und löst alle Aufrufe einer einzelnen kompilierten Funktion auf, sodass nur eine in der endgültigen Binärdatei des Programms / der Bibliothek vorhanden ist.) Um jedoch jede der Quelldateien zu kompilieren, musste die Funktion instanziiert werden und kompiliert, was einige Zeit in Anspruch nahm.
Es reicht aus, wenn der Linker seine Arbeit erledigt, wenn sich der Hauptteil der Funktion in einer Objektdatei befindet. Das manuelle Instanziieren der Vorlage in einer Quelldatei ist eine Möglichkeit, den Compiler dazu zu bringen, den Hauptteil der Funktion in die Objektdatei der betreffenden Quelldatei einzufügen. (Es ist ein bisschen so, als ob die Funktion aufgerufen würde, aber die Instanziierung wird an einer Stelle geschrieben, an der ein Funktionsaufruf ungültig wäre.) Wenn dies erledigt ist, können alle Dateien, die Ihre Funktion aufrufen, kompiliert werden, wobei nur der Header der Funktion bekannt ist Dies spart Zeit, um den Funktionskörper bei jedem Aufruf zu instanziieren und zu kompilieren.
Der zweite Grund (Ausblenden der Implementierung) könnte jetzt sinnvoll sein. Wenn eine Bibliotheksautorin möchte, dass Benutzer ihrer Vorlagenfunktion die Funktion verwenden können, gibt sie ihnen normalerweise den Code der Vorlage, damit sie ihn selbst kompilieren können. Wenn sie den Quellcode der Vorlage geheim halten wollte, konnte sie die Vorlage in dem Code, den sie zum Erstellen der Bibliothek verwendet, manuell instanziieren und den Benutzern die so erhaltene Objektversion anstelle der Quelle geben.
Ist das sinnvoll?
quelle
template void g<double>(double);
sogenannte manuelle Instanziierung (beachten Sie dietemplate
ohne spitze Klammern, das ist ein Unterscheidungsmerkmal der Syntax); Dadurch wird der Compiler angewiesen, eine Instanz der Methode zu erstellen. Hier hat es wenig Wirkung, wenn es nicht da wäre, würde der Compiler die Instanz an der Stelle generieren, an der die Instanz aufgerufen wird. Die manuelle Instanziierung wird selten verwendet. Ich werde sagen, warum Sie sie möglicherweise verwenden möchten, nachdem Sie bestätigt haben, dass die Sache jetzt klarer ist :-)