Template und op-überladung



  • Vielleicht reicht es ja schon aus, den Operator erst zu deklarieren und dann als Freund anzumelden (zur Not mit einer Forward-Deklaration).



  • kannst du das kurz mla mit code zeigen?
    versteh das nicht ganz!



  • //Forward-Deklaration(en):
    template<typename T>
    class test;
    template<typename T>
    ostream& operator<<(ostream&,const test<T>&);
    
    //Klassendefinition:
    template<typename T>
    class test
    {
      ...
      friend ostream& operator<<(ostream&,const test<T>&);//eventuell reicht hier auch 'const test&'
      ...
    };
    
    //Operatordefinition:
    template<typename T>
    ostream& operator<<(ostream& out,const test<T>& obj)
    {...}
    


  • Foku(s) schrieb:

    #pragma argsused
    template<typename T>
    class test
    {
     public:
    
       test(T value):data(value),Style(open){}
       const string& ReturnState() const
          {
           return Names[Style];
          }
    
       friend ostream& operator<<(ostream& os,const test<T>& t);
     private:
      T data;
      static const string Names[2];
      enum styles{open,closed}       Style;
    };
    
    template<typename T>
    ostream& operator<<(ostream& os,const test<T>& t)
    {
     os<<t.Names[t.Style]<<" und: "<<t.data;
     return os;
    }
    template<typename T>
    const string test<T>::Names[2]={"open","closed"};
    

    Schau mal, wenn dein Compiler deine Template Klasse mit T=int definiert, dann haste du in deiner Klasse ja sowas stehen:

    friend ostream& operator<<(ostream& os,const test<int>& t);
    

    D.h. du sagst das es eine friend-funktion mit dieser Signatur gibt, aber die gibt es nicht, sondern nur das:

    template<typename T>
    ostream& operator<<(ostream& os,const test<T>& t)
    {
     os<<t.Names[t.Style]<<" und: "<<t.data;
     return os;
    }
    

    Aber hier spielt das T=int keine Rolle, deswegen musst du deine friend-funktion in der Klasse definieren 😉

    #pragma argsused
    template<typename T>
    class test
    {
     public:
    
       test(T value):data(value),Style(open){}
       const string& ReturnState() const
          {
           return Names[Style];
          }
    
       friend ostream& operator<<(ostream& os,const test<T>& t)
       {
         os<<t.Names[t.Style]<<" und: "<<t.data;
         return os;
       }
     private:
      T data;
      static const string Names[2];
      enum styles{open,closed}       Style;
    };
    


  • also das funktioniert jetzt!

    @Freak_Coder: könntest du mir aber bitte erklären warum?
    Sollte ich jeden operator so in der klasse definieren?
    Mir ist nicht ganz kla, warum er hier außerhalb nicht funktioniert, nur wegen dem int?



  • Also...
    wäre deine Klasse keine Template Klasse würde das ohne Probleme funktionieren so wie du es vorher hattest:

    class MyClass {
      //...
      friend ostream& operator<<(ostream& stream, const MyClass& obj);
    };
    

    Hier sagst du ja dem Compiler das es irgendwo GENAU diese Funktion gibt, also sucht er nach der Definition und findet diese dann zB weiter unten...

    Alles ohne Templates kein Problem da die mit friend bekannt-gemachte Funktion auch tatsächlich definiert ist.

    Jetzt aber zu Templates:

    template <class T> class MyClass {
      //...
      friend ostream& operator<<(ostream& stream, const MyClass<T>& obj); 
    /*<T> kann auch weggelassen werden, da alle MyClass auto. 
     in Class-MyClass MyClass<T> sind, hier nur zum veranschaulichen*/
    };
    
    template<class T>
    ostream& operator<<(ostream& stream, const MyClass<T>& obj)
    {
      //...
    }
    

    Wenn du jetzt irgendwo schreibst:

    MyClass<int> obj;
    

    Dann kreiert der Compiler sowas hier:

    class MyClass {
      //...
      friend ostream& operator<<(ostream& stream, const MyClass<int>& obj); 
    };
    
    template<class T>
    ostream& operator<<(ostream& stream, const MyClass<T>& obj)
    {
      //...
    }
    

    Wie du siehst werden die Parameter bei friend aufgelöst und da steht schön das int.

    Doch unten bei der Definition von op<< steht immer noch T, denn was hat op<< mit MyClass zu tun ? außer das er friend ist. Nämlich gar nichts, deswegen bleiben die Template-Parameter von op<< unangetastet.

    Das Problem kann man also nur lösen indem man op<< als friend deklariert und gleichzeitig auch in der Klasse definiert:

    template <class T> class MyClass {
      //...
      friend ostream& operator<<(ostream& stream, const MyClass<T>& obj)ostream&   
      {
        //...
      }
    };
    

    Jetzt wird da auch bei einer Instanzierung direkt das Template aufgelöst 😉

    Edit:: Effektiv C++ ( 3. Auflage) gibt es ein Kapitel dazu 👍


  • Mod

    Freak_Coders Beitrag ist soweit richtig, aber die Begründung bleibt - für mich - etwas neblig. Auch das angesprochene Kapitel in EffC++ könnte meiner Ansicht nach in dieser Hinsicht klarer sein.

    Foku(s) schrieb:

    template<typename T>
    class test
    {
    ...
       template<typename T>
       friend ostream& operator<<(ostream& os,const test<T>& t);
    };
    

    Fehler:

    [C++ Fehler] Unit1.cpp(47): E2335 Überladene Funktion 'operator ostream & << <int>(ostream &,const test<int> &)' ist in diesem Kontext mehrdeutig

    T ist bereits der Parameter des Klassentemplates test und darf nicht im selben Scope redeklariert werden (also auch nicht als Templateparameter des operators<<).
    Richtig wäre also

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

    Falls dein Compiler oder Linker ein Problem damit hat, ist das kein Problem der Sprache an sich, sondern eines der Umsetzung durch den entsprechenden Compiler - Der Fehlercode lässt auf den Borlandcompiler schließen, welcher nicht gerade für besonders gute Umsetzung des Standards bekannt ist.

    Die folgende alternativen Lösungen versuchen ein anderes Problem zu lösen.

    CStoll schrieb:

    Du brauchst an dieser Stelle kein verschachteltest Template - jede Spezialisierung von test benötigt nur ihren eigenen op<< als Freund und nicht alle Operatoren:

    template<typename T>
    class test
    {
    public:
    friend ostream& operator<<(ostream&, const test<T>&);
    };
    

    An einem solchen Operator ist im Grunde nichts auszusetzen, wenn da nicht die Schwierigkeiten mit der Definition wären. Dieser Operator ist keine Templatefunktion oder ein Funktionstemplate - dass einer seiner Parameter eine Templateklasse ist, spielt keine Rolle. Allerdings hat jede Instanz des Templates test ihren eigenen friend operator<< und es gibt keine Möglichkeit, diese allgemein außerhalb der Klasse zu definieren, eben weil die Operatoren selbst gewöhnliche Funktionen sind. Man müsste für jede verwendete Instanz eine eigene Definition liefern. Das ist der Grund, warum in diesem Falle die Definition zusammen mit der friend Deklaration erfolgen sollte, wie von Freak_Coder empfohlen.

    Foku(s) schrieb:

    template<typename T>
    class test
    {
    ...
       friend ostream& operator<<(ostream& os,const test<T>& t);
    ...
    };
    
    template<typename T>
    ostream& operator<<(ostream& os,const test<T>& t)
    {
     os<<t.Names[t.Style]<<" und: "<<t.data;
     return os;
    }
    ...
    

    Warum das nicht geht, sollte jetzt klar sein. Die friend Deklaration bezieht sich auf eine gewöhnliche Operatorfunktion, die mit dem später deklarierten Template nichts zu tun hat. Allerdings wird diese gewöhnliche Funktion dann bei der Anwendung dem Template vorgezogen, weil beide gleich gut hinsichtlich der Überladungsauflösung sind. Im Übrigen ließe sich der Templateoperator auch nicht instantiieren, eben weil diese Instanzt nicht friend der Klasse ist.

    Wir haben jetzt also zwei Möglichkeiten besprochen:
    1. Der Operator ist eine Funktionstemplate, und alle Instanzen dieses Templates sind friend jeder Instanz des Klassentemplates
    2. Der Operator ist eine gewöhnliche Funktion, dann muss diese (für das Primärtemplate der Klasse) zwingend (wenn wir beliebige Templateparameter zulassen wollen) inline in einer Frienddeklaration definiert werden.
    Die 3. Möglichkeit ist, den Operator als Templatefunktion zu definieren, aber nur diejenige Instanz, deren Funktionsparameter zur jeweiligen Instanz des Klassentemplates passt, dort als friend zu deklarieren. Wir beziehen uns also in der friend Deklaration auf eine Spezialisierung des Templates, das setzt eine vorherige Deklaration des Primärtemplates voraus, was wir nicht innerhalb der Klasse tun können (andernfalls hätten wir ja wieder Fall 1). Das wiederum setzt voraus, dass wir das Klassentemplate vor der Deklaration des Operators deklarieren. also

    template<typename T>
    class test;
    template<typename T>
    ostream& operator<<(ostream&,const test<T>&); // Hier Bezug auf das Template test, daher vorherige Deklaration von test nötig
    
    template<typename T>
    class test
    {
    ...
    // jetzt Bezug auf eine Spezialisierung von operator<<
      friend ostream& operator<< <T>(ostream&,const test<T>&); // oder nur test, aber das <T> beim Funktionsnamen ist entscheidend!
    ...
    };
    
    template<typename T>
    ostream& operator<<(ostream& out,const test<T>& obj)
    {...}
    


  • Hi,

    theoretisch müsse es doch auch noch gehen, eine passende Spezialisierung vom operator<<() zur Verfügung zu stellen, oder:

    template <typename T>
    class test {
       ...
       friend ostream& operator<< (ostream& stream, A<T> const & a);
    };
    
    // Spezialisierung für int
    ostream& operator<<(ostream& stream, A<int> const & a) {
     ...
    }
    
    int main()
    {
       A<int> a;
       cout << a << "\n";
    }
    

    Wenn ich ein A instantiieren will, für das es kein passendes operator<<() gibt, unterbindet das natürlich der Linker...

    Ist häßlich und wird auf mit einer "Warning" (klingt allerdings eher nach einer "wollen Sie das wirklich?"-Meldung) bestraft .... aber ist es wirklich unkorrekt ?

    Gruß,

    Simon2.


  • Mod

    Simon2 schrieb:

    Hi,

    theoretisch müsse es doch auch noch gehen, eine passende Spezialisierung vom operator<<() zur Verfügung zu stellen, oder:

    Das kannst du tun (deshalb meine etwas umständliche Einschränkung für Punkt 2 - wenn wir beliebige Templateparameter zulassen wollen). Nur die Wortwahl ist ungünstig. Es handelt sich nicht um eine (zusätzliche) Spezialisierung sondern um eine Überladung.



  • camper schrieb:

    Simon2 schrieb:

    Hi,

    theoretisch müsse es doch auch noch gehen, eine passende Spezialisierung vom operator<<() zur Verfügung zu stellen, oder:

    Das kannst du tun (deshalb meine etwas umständliche Einschränkung für Punkt 2 - wenn wir beliebige Templateparameter zulassen wollen). Nur die Wortwahl ist ungünstig. Es handelt sich nicht um eine (zusätzliche) Spezialisierung sondern um eine Überladung.

    Hmmm, ...*grübel*
    ... unter "Überladung" verstehe ich die Existenz von mindestens 2 Funktionen mit demselben Namen aber unterschiedlicher Signatur ....
    ... "Spezialisierung" setzt ein "generaleres template" voraus, ....

    => Du hast also (wie immer 😉 ) Recht !

    Gruß,

    Simon2.


Anmelden zum Antworten