[solved] wieder mal: templates vs. iostream
-
TyRoXx schrieb:
So geht es bei mir:
Ist aber nicht das Gleiche! Jetzt ist jede Templateklasse Freund aller anderen
operator<<Templatefunktionen. Dabei sollte die Templateklasse nur Freund der Templatefunktion mit dem gleichen Parameter sein.@iko79,
Zuerst eine Korrektur, dann etwas Theorie und am Ende die Lösung
iko79 schrieb:
MSVC 9.0 schrieb:
1>main.obj : error LNK2019: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl ns::operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class ns::MyClass<int> const &)" (??6ns@@YAAAV?char_traits@D@std@@@std@@AAV12@ABV?$MyClass@H@0@@Z) referenced in function _main
es wird offensichtlich im falschen namespace gesucht ("ns::operator<<"), ...
Das was du hier vor dir hast, ist kein Kompilerfehler sondern ein Linkerfehler. Die Definition der Funktion kann nicht gefunden werden. Um genau zu sein, wird der freie
operator <<gar nie instanziert. Es existiert somit beim Linken gar kein solcher Operator, bzw. es existiert nur eine Deklaration von diesem in deiner Klasse.Das mag etwas verwirrend klingen, daher probiere ich es dir mal am Code aufzuzeigen:
int main( int argc, char **argv ) { ns::MyClass<int> mc( 10 ); // Es wurde zum ersten Mal ns::MyClass<int> verwendet, daher wird hier // die Templateklasse mit int instanziert. Dadurch wird auch eine // Funktionsdeklaration erstellt, nämlich: // std::ostream& operator <<(std::ostream&, ns::MyClass<int> const&); std::cout << mc << std::endl; // Um diese Ausgabe zu machen, wird der beste passende operator << gesucht. // Dank ADL (Argument Dependent Lookup) wird zuerst im Scope der Klasse // ns::MyClass<int> gesucht. Und dort findet es dann die Deklaration // des operator<<. Diese Deklaration reicht dem Kompiler völlig, er braucht // keine Definition, um kompilieren zu können. }Wie du siehst, hat der Kompiler gar nie etwas mit dem Funktionstemplate zu tun gehabt. Dieses Template ist schliesslich in einem anderen Scope und dort wurde gar nie gesucht. Es wurde gar nie eine Definition instanziert, wodurch der Linker am Ende meckert, dass es gar keine Definition gibt.
Um dies zu beheben, musst du dem Kompiler irgendwie mitteilen, dass die Funktion in der Klasse mit einem Funktionstemplate ausserhalb der Klasse zusammenhängt. Damit dies überhaupt möglich ist, musst du die Funktionstemplatedefinition (geht auch nur die Deklaration) vor die Klasse ziehen, sonst wäre diese gar nicht bekannt. Mit dem Scope-Operator gibst du dann noch an, in welchem Scope gesucht werden soll. Am Ende sieht dies dann etwa so aus:
namespace ns { template <class T> class MyClass ; template <class T> std::ostream &operator << ( std::ostream &ostr, const MyClass<T> &mc ) { ostr << mc.value; return ostr; } template <class T> class MyClass { private: T value; public: MyClass( T i ) : value( i ) {} friend std::ostream &::ns::operator << ( std::ostream &ostr, const MyClass<T> &mc ); // ^^^^^^ Angabe wo die Funktion gefunden werden kann }; } int main( int argc, char **argv ) { ns::MyClass<int> mc( 10 ); std::cout << mc << std::endl; }Aber vorsicht nicht template Funktionen werden bevorzugt. Wobei dieser Fehler eher seltener passieren sollte, also dass man nicht template Funktionen mit der gleichen Signatur hat.
Als Alternative steht dir noch die Möglichkeit zur Verfügung die Funktionstemplatedefinition gleich inline in der Klasse zu machen und auf die externe Funktion zu verzichten. Oder wenn die Funktion über Getter implementiert werden kann, dann mache sie nicht zu einem
friend. Das erleichtert die Sache auch noch um einiges.Ich empfehle wenn möglich
friend & templateszu vermeiden. Hat meiner Meinung nach viel zu viele Fallstricke.Grüssli
-
Hi Dravere,
danke für die ausführliche antwort!
Dravere schrieb:
TyRoXx schrieb:
So geht es bei mir:
Ist aber nicht das Gleiche! Jetzt ist jede Templateklasse Freund aller anderen
operator<<Templatefunktionen.also ich habe anstelle von U (wie bei TyRoXx) T verwendet, das sollte wieder passen, oder nicht?
Dravere schrieb:
Dabei sollte die Templateklasse nur Freund der Templatefunktion mit dem gleichen Parameter sein.
@iko79,
Zuerst eine Korrektur, dann etwas Theorie und am Ende die Lösung
iko79 schrieb:
MSVC 9.0 schrieb:
1>main.obj : error LNK2019: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl ns::operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class ns::MyClass<int> const &)" (??6ns@@YAAAV?char_traits@D@std@@@std@@AAV12@ABV?$MyClass@H@0@@Z) referenced in function _main
es wird offensichtlich im falschen namespace gesucht ("ns::operator<<"), ...
Das was du hier vor dir hast, ist kein Kompilerfehler sondern ein Linkerfehler. Die Definition der Funktion kann nicht gefunden werden. Um genau zu sein, wird der freie
operator <<gar nie instanziert. Es existiert somit beim Linken gar kein solcher Operator, bzw. es existiert nur eine Deklaration von diesem in deiner Klasse.Das mag etwas verwirrend klingen, daher probiere ich es dir mal am Code aufzuzeigen:
int main( int argc, char **argv ) { ns::MyClass<int> mc( 10 ); // Es wurde zum ersten Mal ns::MyClass<int> verwendet, daher wird hier // die Templateklasse mit int instanziert. Dadurch wird auch eine // Funktionsdeklaration erstellt, nämlich: // std::ostream& operator <<(std::ostream&, ns::MyClass<int> const&); std::cout << mc << std::endl; // Um diese Ausgabe zu machen, wird der beste passende operator << gesucht. // Dank ADL (Argument Dependent Lookup) wird zuerst im Scope der Klasse // ns::MyClass<int> gesucht. Und dort findet es dann die Deklaration // des operator<<. Diese Deklaration reicht dem Kompiler völlig, er braucht // keine Definition, um kompilieren zu können. }Wie du siehst, hat der Kompiler gar nie etwas mit dem Funktionstemplate zu tun gehabt. Dieses Template ist schliesslich in einem anderen Scope und dort wurde gar nie gesucht. Es wurde gar nie eine Definition instanziert, wodurch der Linker am Ende meckert, dass es gar keine Definition gibt.
Um dies zu beheben, musst du dem Kompiler irgendwie mitteilen, dass die Funktion in der Klasse mit einem Funktionstemplate ausserhalb der Klasse zusammenhängt.
das ist exakt das, was ich abgekürzt mit "es wird offensichtlich im falschen namespace gesucht" meinte
jedenfalls war das meine vermutung.Dravere schrieb:
Damit dies überhaupt möglich ist, musst du die Funktionstemplatedefinition (geht auch nur die Deklaration) vor die Klasse ziehen, sonst wäre diese gar nicht bekannt. Mit dem Scope-Operator gibst du dann noch an, in welchem Scope gesucht werden soll. Am Ende sieht dies dann etwa so aus:
namespace ns { template <class T> class MyClass ; template <class T> std::ostream &operator << ( std::ostream &ostr, const MyClass<T> &mc ) { ostr << mc.value; return ostr; } template <class T> class MyClass { private: T value; public: MyClass( T i ) : value( i ) {} friend std::ostream &::ns::operator << ( std::ostream &ostr, const MyClass<T> &mc ); // ^^^^^^ Angabe wo die Funktion gefunden werden kann }; } int main( int argc, char **argv ) { ns::MyClass<int> mc( 10 ); std::cout << mc << std::endl; }leuchtet ein.
Dravere schrieb:
Oder wenn die Funktion über Getter implementiert werden kann, dann mache sie nicht zu einem
friend. Das erleichtert die Sache auch noch um einiges.also das mit den gettern verstehe ich jetzt nicht. ich soll einen operator als getter implementieren? wie würde sowas aussehen? oder hab ich das jetzt falsch verstanden? ich hab grad GAR keine ahnung, was du meinst.
-
iko79 schrieb:
also das mit den gettern verstehe ich jetzt nicht. ich soll einen operator als getter implementieren? wie würde sowas aussehen? oder hab ich das jetzt falsch verstanden? ich hab grad GAR keine ahnung, was du meinst.
Er meint, du sollst nicht direkt auf die privaten Daten zugreifen, sondern Get-Funktionen einrichten.
template <typename T> class MyClass { private: T value; public: T GetValue() const { return value; } // ... };
-
iko79 schrieb:
also das mit den gettern verstehe ich jetzt nicht. ich soll einen operator als getter implementieren? wie würde sowas aussehen? oder hab ich das jetzt falsch verstanden? ich hab grad GAR keine ahnung, was du meinst.
Ich denk mal er meint du sllst einen Getter implementieren für die Elemente, die beim op<< ausgegeben werden sollen, um dir die friend-Deklaratoin sparen zu können. Davon würd ich aber abraten, da du damit diese Member nicht nur dem op<< sondern allen Benutzern der Klasse offenlegst. Um die friend-Deklaration zu vermeiden kann man eher folgendes machen:
namespace ns { template <class T> class Foo { int value; public: ostream& print(ostream& os) const { os << value; return os; } }; template <class T> inline ostream& operator<< (ostream& os, Foo<T>const& f) { return f.print(os); } }//end namespace ns
-
achsooo, allesklar.
besten dank an euch drei!
-
pumuckl schrieb:
Davon würd ich aber abraten, da du damit diese Member nicht nur dem op<< sondern allen Benutzern der Klasse offenlegst.
Das hängt davon ab, ob
valuenur ein Implementierungsdetail oder eine tatsächliche Eigenschaft der Klasse repräsentiert, auf welche man auch sonst Zugriff haben darf. Da der Member immerhin ausgegeben werden soll, liegt es von mir aus gesehen nahe, dass es sich eben um kein Implementierungsdetail handelt.Die Frage ist auch, ob eine
print()-Funktion viel besser ist, wenn sie lediglich als Indirektion desoperator<<benutzt wird. Nur um keinfriendzu haben, ich weiss ja nicht...
Eine Alternative wäre auch sowas wiestd::string ToString() const, was sich aber auch erst wirklich auszahlt, wenn man die Stringrepräsentation auch noch an anderen Orten benötigt. Ansonsten scheint mir derfriend operator<<gar nicht mal so abwegig.
-
iko79 schrieb:
also ich habe anstelle von U (wie bei TyRoXx) T verwendet, das sollte wieder passen, oder nicht?
Es darf aber über der friend Deklaration auch kein
template<typename T>haben. Einfach nur so, wie ich es gezeigt habe.iko79 schrieb:
das ist exakt das, was ich abgekürzt mit "es wird offensichtlich im falschen namespace gesucht" meinte
jedenfalls war das meine vermutung.Da ist aber ein wenig sehr heftig abgekürzt. Ich hoffe aber mal, dass du den Unterschied verstanden hast zwischen Linker und Kompiler in diesem Fall. Es sind verdammt viele verschiedene Konzepte hier im Einsatz, damit dieser Fehler entsteht.
pumuckl schrieb:
Ich denk mal er meint du sllst einen Getter implementieren für die Elemente, die beim op<< ausgegeben werden sollen, um dir die friend-Deklaratoin sparen zu können.
Nicht ganz, ich meinte nur, falls es bereits Getter hat, dass man diese verwenden soll. Ich habe schon viele gesehen, welche eine freie Funktion als
frienddeklarierten, obwohl sie diese auch einfach mit den bereits vorhandenen Gettern der Klasse schreiben hätten können.@Nexus,
Die Printfunktion hat einen kleinen netten Vorteil. Man kann sievirtualmachen.Grüssli
-
Nexus schrieb:
Die Frage ist auch, ob eine
print()-Funktion viel besser ist, wenn sie lediglich als Indirektion desoperator<<benutzt wird. Nur um keinfriendzu haben, ich weiss ja nicht...Siehs andersrum: Die print-Methode ist Teil des Klasseninterfaces und braucht Zugriff auf private Elemente, muss also selbst Methode der Klasse sein.
Der op<< ist nur der Bonus dazu, der die gewohnte Ausgabe und das Chaining möglich macht. Die friend-Deklaration ist imo eine Unart. Statt erst das Paket ordentlich zu schnüren (ein ordentliches Interface wo alles was Methode sein muss auch Methode ist und alles was freie Funktion sein kann auch freie Funktion ist), und dann die Schleife (den schöner lesbaren op<<) drumzumachen, wird das Paket und seine Kapselung mit dem friend-Brecheisen aufgehebelt um die Schleife dranzubäppen.
-
Dravere schrieb:
iko79 schrieb:
also ich habe anstelle von U (wie bei TyRoXx) T verwendet, das sollte wieder passen, oder nicht?
Es darf aber über der friend Deklaration auch kein
template<typename T>haben. Einfach nur so, wie ich es gezeigt habe.achja.
Dravere schrieb:
iko79 schrieb:
das ist exakt das, was ich abgekürzt mit "es wird offensichtlich im falschen namespace gesucht" meinte
jedenfalls war das meine vermutung.Da ist aber ein wenig sehr heftig abgekürzt. Ich hoffe aber mal, dass du den Unterschied verstanden hast zwischen Linker und Kompiler in diesem Fall. Es sind verdammt viele verschiedene Konzepte hier im Einsatz, damit dieser Fehler entsteht.
ich habe die erfahrung gemacht, dass fragen umso weniger beantwortet werden, umso ausführlicher man sie formuliert. vermutlich clicken viele einfach gleich weiter wenn sie 'nen langen text sehen. darum beschränke ich mich seit einiger zeit auf das absolut notwendigste wenn ich 'ne frage poste. auch wenn ich mich mit template-syntax immer wieder verheddere -- ich bin absolut kein anfänger und weiß sehr wohl was compiler und linker sind
long story short -- ja, ich habs verstanden und genau genommen ists genau der fehler, den ich vermutet habe, ich wusste nur nicht, wie ich ihn beseitigen soll.iko79 schrieb:
achsooo, allesklar.
besten dank an euch drei!an euch vier, sorry! hab den ersten post von Nexus ganz übersehen....
-
iko79 schrieb:
auch wenn ich mich mit template-syntax immer wieder verheddere -- ich bin absolut kein anfänger und weiß sehr wohl was compiler und linker sind

Dann empfehle ich dir "C++ Templates - the Complete Guide" von Josuttis/Vandervoorde. Die gehn auf alle Details ein, auch Compilertechnisch gesehen, dass man ein ziemlich guted Verständnis von dem bekommen kann, was da passiert und entsprechend auch schnell weiß was hinter einer Fehlermeldung steckt.
-
iko79 schrieb:
ich habe die erfahrung gemacht, dass fragen umso weniger beantwortet werden, umso ausführlicher man sie formuliert. vermutlich clicken viele einfach gleich weiter wenn sie 'nen langen text sehen. darum beschränke ich mich seit einiger zeit auf das absolut notwendigste wenn ich 'ne frage poste.
Das ist grundsätzlich schon richtig, nur sollte die Problemstellung trotzdem korrekt und vollständig sein. Ich klicke oft auch Beiträge weg, wo es mich einfach nur nervt, weil wieder die Hälfte fehlt oder man denjenigen zuerst über den Fehler aufklären muss, was er wirklich bedeutet

iko79 schrieb:
ja, ich habs verstanden und genau genommen ists genau der fehler, den ich vermutet habe, ich wusste nur nicht, wie ich ihn beseitigen soll.
Dann ist ja gut, wollte nur sicher gehen.
pumuckl schrieb:
Dann empfehle ich dir "C++ Templates - the Complete Guide" von Josuttis/Vandervoorde.
Kann ich nur unterstreichen:
C++ Templates | ISBN: 9780201734843Habe auch in diesem Fall schnell in dem Buch nachgeschlagen, um sicher zu sein, dass ich keinen Mist erzähle

Grüssli
-
pumuckl schrieb:
Siehs andersrum: Die print-Methode ist Teil des Klasseninterfaces und braucht Zugriff auf private Elemente, muss also selbst Methode der Klasse sein.
Ja, von der Kapselung her gesehen ist eine Memberfunktion tatsächlich schöner. Zumindest auf den ersten Blick...
Ich meinte eher, dass dadurch nicht nur Vorteile entstehen. Zum einen ist das Interface grösser (soll ich
obj.print(stream)oderstream << objschreiben?), zum anderen kann es oft sein, dass gar kein Zugriff auf private Member notwendig ist, weil ausreichend Getter bereitstehen. Soll man in diesen Fällen auch eineprint()-Memberfunktion einrichten? Damit würde man die Kapselung unnötigerweise verringern. Andererseits ist es inkonsistent und für den Benutzer verwirrend, wenn ein paar Klassenoperator<<undprint(), ein paar andere hingegen nuroperator<<unterstützen. Dazu kommt, dassoperator<<als Stream-Operator den gängigen Konventionen entspricht, die jeder C++-Programmierer kennt. Beiprint()muss man sich genau in Dokumentationen einlesen, weil es jeder etwas anders macht (bzw. gar nicht, wie die Standardbibliothek).Meiner Meinung nach ist
friendlegitim, wenn es im Endeffekt die Kapselung vergrössert, indem es andere Funktionen unnötig macht. Ob das hier der Fall ist, darüber kann man streiten...