2009-11-24 16 views
9

Ich hatte mit dem Problem in this question beschrieben (Erklären einer Vorlage Funktion als Freund einer Vorlage Klasse), und ich glaube, die zweite Antwort ist was ich tun will (forward deklarieren Sie die Vorlage Funktion, dann benennen Sie eine Spezialisierung als Freund). Ich habe eine Frage zu, ob eine etwas andere Lösung tatsächlich richtig ist oder passiert einfach in Visual C++ arbeiten 2008Vorlage Freund Funktion einer Vorlage Klasse

Prüfregeln ist:

#include <iostream> 

// forward declarations 
template <typename T> 
class test; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t); 

template <typename T> 
class test { 
    friend std::ostream& operator<< <T>(std::ostream &out, const test<T> &t); 
    // alternative friend declaration 
    // template <typename U> 
    // friend std::ostream& operator<<(std::ostream &out, const test<T> &t); 

    // rest of class 
    }; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t) { 
    // output function defined here 
    } 

Zuerst eine seltsame Sache, die ich fand, war, dass, wenn ich ändern die Vorwärtsdeklaration von operator<<, so dass es nicht übereinstimmt (zum Beispiel std::ostream& operator<<(std::ostream &out, int fake);, alles kompiliert noch und funktioniert einwandfrei (um klar zu sein, muss ich eine solche Funktion nicht definieren, nur deklarieren). Wie in der linked-to-Frage, das Entfernen der Forward-Deklaration verursacht ein Problem, da der Compiler zu denken scheint, dass ich ein Datenmember statt einer Friend-Funktion deklariere. Ich bin ziemlich sicher, dass dieses Verhalten ist ein Visual C++ 2008 Fehler.

Das interessante Ding ist, wenn ich die Vorwärtsdeklarationen entferne und die alternative Freunddeklaration im Code oben verwende. Beachten Sie, dass der Vorlagenparameter U nicht in der folgenden Signatur angezeigt wird. Diese Methode kompiliert und funktioniert auch korrekt (ohne etwas anderes zu ändern). Meine Frage ist, ob dies dem Standard oder einer Idiosynkrasie von Visual C++ 2008 entspricht (ich konnte in meinen Nachschlagewerken keine gute Antwort finden).

Beachten Sie, dass, während ein Freund Erklärung template <typename U> friend ... const test<U> &t); auch funktioniert, das gibt tatsächlich jede Instanz des Betreibers friend Zugriff auf jede Instanz von test, während das, was ich will, ist, dass die privaten Mitglieder test<T> nur von operator<< <T> zugänglich sein sollte. Ich testete dies, indem ich eine test<int> innerhalb der operator<< instanziieren und auf ein privates Mitglied zugreife; Dies sollte einen Kompilierfehler verursachen, wenn ich versuche, eine test<double> auszugeben.

Synopsis: Das Entfernen der Forward-Deklarationen und das Wechseln zu der alternativen Friend-Deklaration in dem obigen Code scheint dasselbe Ergebnis (in Visual C++ 2008) zu produzieren - ist dieser Code tatsächlich richtig?

UPDATE: Jede der oben genannten Änderungen am Code funktionieren nicht unter gcc, also vermute ich, dass dies Fehler oder "Features" im Visual C++ - Compiler sind. Trotzdem würde ich gerne Einsichten von Leuten, die mit dem Standard vertraut sind, schätzen.

+1

Übrigens habe ich herausgefunden, dass das Hinzufügen von 'namespace std;' vor der Klassendeklaration die Notwendigkeit für die Forward-Deklarationen entfernt, was ich vermute, ist ein Nebeneffekt des (möglichen) Compiler-Bugs. –

+0

Laut meinem Kommentar - können Sie explizit die Beispiele (einschließlich Instanziierung), die Sie sagen, arbeiten? Verbal ist es ziemlich schwer zu verstehen, was Sie für jedes Beispiel meinen. –

Antwort

7

... wenn ich die Vorwärts-Erklärung des Betreibers ändern < <, so dass es nicht

Ein Freund Funktion als eine ganz besondere Art der Anmeldung sollte gesehen überein. Im Wesentlichen tut der Compiler genug, um die Deklaration zu analysieren, aber es findet keine semantische Überprüfung statt, es sei denn, Sie spezialisieren die Klasse tatsächlich.

Nach der vorgeschlagenen Änderung zu machen, wenn Sie dann test instanziiert erhalten Sie einen Fehler über die Erklärungen erhalten nicht passend:

template class test<int>; 

... aber ... die Vorwärtsdeklaration zu entfernen verursacht ein Problem

Der Compiler versucht, die Deklaration zu analysieren, um sie zu speichern, bis die Klassenvorlage spezialisiert ist.Beim Parsen erreicht der Compiler die < in der Erklärung:

friend std::ostream& operator<< < 

Der einzige Weg, dass operator<< von < gefolgt werden könnte, ist, wenn es eine Vorlage, so dass ein Lookup erfolgt zu überprüfen, ob es sich um eine Vorlage ist. Wenn eine Funktionsvorlage gefunden wird, wird < als der Start von Vorlagenargumenten betrachtet. Wenn Sie die Forward-Deklaration entfernen, wird keine Vorlage gefunden und operator<< wird als ein Objekt betrachtet. (Dies ist auch der Grund, warum der Code bei der Hinzufügung using namespace std weiterhin kompiliert wird, da es Deklarationen von Vorlagen für operator<< geben muss).

... wenn ich die Forward-Deklarationen entfernen und die alternative Freundesdeklaration im obigen Code verwenden. Beachten Sie, dass der Vorlagenparameter U in der folgenden Signatur nicht erscheint ...

Es ist nicht erforderlich, dass alle Vorlagenparameter in den Argumenten einer Funktionsvorlage verwendet werden. Die alternative Deklaration bezieht sich auf eine neue Funktionsvorlage, die nur dann aufgerufen werden kann, wenn sie im Namespace deklariert ist und explizite Vorlagenargumente angibt.

Ein einfaches Beispiel hierfür wäre:

class A {}; 
template <typename T> A & operator<<(A &, int); 

void foo() { 
    A a; 
    operator<< <int> (a, 10); 
} 

... ist dieser Code tatsächlich richtig ..

Nun gibt es zwei Teile dazu. Die erste ist, dass die alternative Friend-Funktion nicht auf die Erklärung bezieht sich später in den Anwendungsbereich:

template <typename T> 
class test { 
    template <typename U> 
    friend std::ostream& operator<<(std::ostream &out, const test<T> &t); 
    }; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t); // NOT FRIEND! 

Die Friend-Funktion tatsächlich im Namensraum für jede Spezialisierung deklariert werden würde:

template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<int> &t); 
template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<char> &t); 
template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<float> &t); 

Jede Spezialisierung operator<< <U> hat Zugriff auf die spezifische Spezialisierung gemäß dem Typ seines Parameters test<T>. Im Wesentlichen ist der Zugriff also eingeschränkt, wie Sie es benötigen. Aber wie ich bereits erwähnt, bevor diese Funktionen im Grunde unbrauchbar als Operatoren sind, da Sie Funktionsaufruf Syntax verwenden müssen:

int main() 
{ 
    test<int> t; 
    operator<< <int> (std << cout, t); 
    operator<< <float> (std << cout, t); 
    operator<< <char> (std << cout, t); 
} 

sich nach den Antworten auf die vorherige Frage, entweder Sie die Vorwärts-Deklaration verwenden, wie von litb vorgeschlagen, oder Sie gehe mit der Definition der Friend-Funktion inline nach Dr_Asik's answer (das wäre wohl was ich machen würde).

UPDATE: 2. Kommentar

... die Vorwärtsdeklaration vor der Klasse zu ändern; immer noch der in der Klasse entspricht die Funktion, die ich später implementieren ...

Wie ich bereits erwähnt, der Compiler prüft, ob operator<< eine Vorlage ist, wenn es die < in der Erklärung sieht:

friend std::ostream& operator<< < 

Dies geschieht, indem Sie den Namen nachschlagen und prüfen, ob es sich um eine Vorlage handelt.Solange Sie eine Dummy-Forward-Deklaration haben, "trickst" dies den Compiler dazu, Ihren Freund als einen Vorlagennamen zu behandeln, und so wird der < als der Beginn einer Template-Argumentliste angesehen.

Später, wenn Sie die Klasse instanziieren, haben Sie eine gültige Vorlage zu entsprechen. Im Wesentlichen trickst du den Compiler einfach dazu, den Freund als Template-Spezialisierung zu behandeln.

Sie können dies hier tun, weil (wie ich bereits sagte) zu diesem Zeitpunkt keine semantische Überprüfung stattfindet.

+0

Ursprünglich dachte ich, dass es so funktioniert, wie du es beschrieben hast, aber es funktioniert nicht (weshalb ich diese Frage gestellt habe). Wenn ich sagte "alles kompiliert und funktioniert ordnungsgemäß", meinte ich tatsächlich, dass ich in der Lage war, die Klasse erfolgreich zu instanziieren und den Operator '' 'in der üblichen Weise zu verwenden. Ich habe auch überprüft, dass die Operatorfunktion den richtigen Zugriff hatte (zB konnte nicht auf private Mitglieder eines 'test ' zugegriffen werden, wenn es aufgerufen wurde, einen 'test ' auszudrucken. Dies ist der Fall mit dem Code wie geschrieben, mit a "falsche" Vorwärtsdeklaration, und mit der alternativen Freundschaftsdeklaration. –

+0

Auch habe ich gemerkt, dass Sie wahrscheinlich missverstanden haben, was ich über die "falsche" Erklärung gesagt habe - ich spreche nur über die Änderung der Vorwärtsdeklaration vor der Klasse, die in der Klasse entspricht immer noch der Funktion, die ich später implementiere (also wurde bei der Instanziierung die benötigte Funktionsvorlage bereits geparst) –

+0

@Sumudu: Ich habe ein aktualisiertes hinzugefügt, um deinen zweiten Kommentar zu adressieren. Was die erste betrifft, kannst du bitte deine ändern Frage, um das Beispiel einzubeziehen, das Sie sagen, "kompiliert und funktioniert ordnungsgemäß" aber verwendet die alternative Vorlage für die Freundesfunktion? Wenn ich das hier mache, erhalte ich Fehler für den Zugriff an die Mitglieder in der Namespace-Vorlage. –

Verwandte Themen