[solved] wieder mal: templates vs. iostream



  • hallo,

    jedes mal wenn ich was auf die art versuche, hab ich probleme. irgendwie kapier' ich das einfach nicht. warum geht das nicht:

    namespace ns
    {
    	template <class T>
    	class MyClass
    	{
    	private: 
    		T value;
    
    	public:
    		MyClass( T i ) : value( i )		{}
    
    		friend std::ostream &operator << ( std::ostream &ostr, const MyClass<T> &mc );
    	};
    }
    
    template <class T>
    std::ostream &operator << ( std::ostream &ostr, const ns::MyClass<T> &mc )
    {
    	ostr << mc.value;
    	return ostr;
    }
    
    int main( int argc, char **argv )
    {
    	ns::MyClass<int> mc( 10 );
    
    	std::cout << mc << std::endl;
    }
    

    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?basic_ostream@DU?basic\_ostream@DU?char_traits@D@std@@@std@@AAV12@ABV?$MyClass@H@0@@Z) referenced in function _main

    ???

    es wird offensichtlich im falschen namespace gesucht ("ns::operator<<"), aber dort gehört der operator ja auch nicht hin. wenn ich die funktionsdefinition in den namespace reinkopiere gehts genauso wenig.

    thx,
    iko.



  • So geht es bei mir:

    namespace ns
    {
        template <class T>
        class MyClass
        {
        private:
            T value;
    
        public:
            MyClass( T i ) : value( i )        {}
    
    		template <class U>
    		friend std::ostream &operator << ( std::ostream &ostr, const MyClass<U> &mc );
        };
    
    	template <class U>
    	std::ostream &operator << ( std::ostream &ostr, const MyClass<U> &mc )
    	{
    		ostr << mc.value;
    		return ostr;
    	}
    }
    


  • hm. okay...

    danke! 🙂


  • Administrator

    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?basic_ostream@DU?basic\_ostream@DU?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 & templates zu 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?basic_ostream@DU?basic\_ostream@DU?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 value nur 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 des operator<< benutzt wird. Nur um kein friend zu haben, ich weiss ja nicht...
    Eine Alternative wäre auch sowas wie std::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 der friend operator<< gar nicht mal so abwegig.


  • Administrator

    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 friend deklarierten, 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 sie virtual machen.

    Grüssli



  • Nexus schrieb:

    Die Frage ist auch, ob eine print() -Funktion viel besser ist, wenn sie lediglich als Indirektion des operator<< benutzt wird. Nur um kein friend zu 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.


  • Administrator

    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: 9780201734843

    Habe 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) oder stream << obj schreiben?), 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 eine print() -Memberfunktion einrichten? Damit würde man die Kapselung unnötigerweise verringern. Andererseits ist es inkonsistent und für den Benutzer verwirrend, wenn ein paar Klassen operator<< und print() , ein paar andere hingegen nur operator<< unterstützen. Dazu kommt, dass operator<< als Stream-Operator den gängigen Konventionen entspricht, die jeder C++-Programmierer kennt. Bei print() muss man sich genau in Dokumentationen einlesen, weil es jeder etwas anders macht (bzw. gar nicht, wie die Standardbibliothek).

    Meiner Meinung nach ist friend legitim, 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...


Anmelden zum Antworten