Wie trennt man Deklaration und Definition von friend-member-template-Funktionen?



  • brotbernd schrieb:

    template <typename STREAM>
    friend STREAM& operator<<(STREAM& os, const Test<T>& rhs);
    

    Du deklarierst hier eine Familie von << operatoren als friend der Klasse Test<T>. Diese sind alle freie Funktionen.
    Zu jeder Test<T> gibt es also eine Reihe von operatoren für verschiedene STREAM Typen.

    template<typename T, typename STREAM>
    STREAM& operator<<(STREAM& os, const Test<T>& rhs)
    {
        os << rhs.value_;
        return os;
    }
    

    Definiert für verschiedene Typen T operatoren für verschiedene Typen STREAM.

    Was meckert der Compiler denn? Ich hab hier grad nur den msvc9, der schluckt das.

    Die Funktionen sind an die Instantiierung der Klasse gebunden. Sie werden entsprechend vom Kompiler für beliebige STREAM typen angelegt, sobald die Klasse mit einem Typ T instantiiert werden. Genau dann aber ist auch der zweite Parameter der Funktion operator<<() an den Typ T gebunden.

    Der gcc (g++) schluckt es auch, wenn die Definition der Funktion direkt in der Klasse erfolgt. Meine Frage war aber, wie man die Definition abtrennt.



  • camper schrieb:

    Yeequ schrieb:

    Nur wie trennt man in diesem Fall operator<<() ab? Ich habe einige Möglichkeiten durchprobiert, aber keine Lösung gefunden.

    Das ist nicht möglich, es sei denn, du implementierst alle benötigten Varianten einzeln.

    template <typename T>
    struct Foo
    {
        template<typename S>
        friend S& operator<<(S& os, const Foo& rhs);
    };
    template<typename S>
    S& operator<<(S& os, const Foo<int>& rhs) { /* ... */ }
    template<typename S>
    S& operator<<(S& os, const Foo<float>& rhs) { /* ... */ }
    // usw.
    

    Sinnvoll? Wahrscheinlich nicht.

    Es ist in der Tat genau die Definition der vorher deklarierten Funktion. Nur hast du recht, es ist wenig hilfreich. Dummerweise kann die Definition scheinbar nicht mehr weiter mit ver-templatet werden, wie template-templates.

    template <typename T>
    template <typename STREAM> 
    STREAM& operator<<(STREAM& os, const Test<T>& rhs) {
        os << rhs.value_;
        return os;
    }
    


  • brotbernd schrieb:

    template <typename STREAM>
    friend STREAM& operator<<(STREAM& os, const Test<T>& rhs);
    

    Du deklarierst hier eine Familie von << operatoren als friend der Klasse Test<T>. Diese sind alle freie Funktionen.
    Zu jeder Test<T> gibt es also eine Reihe von operatoren für verschiedene STREAM Typen.

    template<typename T, typename STREAM>
    STREAM& operator<<(STREAM& os, const Test<T>& rhs)
    {
        os << rhs.value_;
        return os;
    }
    

    Definiert für verschiedene Typen T operatoren für verschiedene Typen STREAM.

    Was meckert der Compiler denn? Ich hab hier grad nur den msvc9, der schluckt das.

    Der GCC Compiler selbst findet das in Ordnung. Anscheinend nutzt er jedoch andere operator<<-Versionen als die Implementierung im Beispiel zur Verfügung stellen könnte so dass der Linker Fehler meldet:

    `

    test.o: test.cpp:(.text+0xb4): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& operator<< <std::basic_ostream<char, std::char_traits<char> > >(std::basic_ostream<char, std::char_traits<char> >&, Test<int> const&)'

    `



  • Ulrich_B schrieb:

    brotbernd schrieb:

    template <typename STREAM>
    friend STREAM& operator<<(STREAM& os, const Test<T>& rhs);
    

    Du deklarierst hier eine Familie von << operatoren als friend der Klasse Test<T>. Diese sind alle freie Funktionen.
    Zu jeder Test<T> gibt es also eine Reihe von operatoren für verschiedene STREAM Typen.

    template<typename T, typename STREAM>
    STREAM& operator<<(STREAM& os, const Test<T>& rhs)
    {
        os << rhs.value_;
        return os;
    }
    

    Definiert für verschiedene Typen T operatoren für verschiedene Typen STREAM.

    Was meckert der Compiler denn? Ich hab hier grad nur den msvc9, der schluckt das.

    Der GCC Compiler selbst findet das in Ordnung. Anscheinend nutzt er jedoch andere operator<<-Versionen als die Implementierung im Beispiel zur Verfügung stellen könnte so dass der Linker Fehler meldet:

    `

    test.o: test.cpp:(.text+0xb4): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& operator<< <std::basic_ostream<char, std::char_traits<char> > >(std::basic_ostream<char, std::char_traits<char> >&, Test<int> const&)'

    `

    Was ist daran verwunderlich? Natürlich kann nicht gelinkt werden, es sind ja zwei unterschiedliche Funktionen. Und für eine gibt es keine Definition, welche aber gebraucht wird. Die andere Funktion ist überflüssig.

    Das ist quasi das gleiche wie hier:

    void print(int);
    void print(char**) { /* print char** */ }
    
    main() {
        print(3);
    }
    


  • Was mich wundert ist dass der MSVC das angeblich schluckt.



  • sorry leute.. mein Fehler.
    Ich hatte (wie man in meinem 1.post sieht) value_ nicht privat. Kein wunder, dass das funktioniert.
    Naja ich war aufm Sprung in die Mittagspause...poste nicht mit leerem Magen ... wie peinlich 🙄

    Ich mach es mit stream Operatoren eh lieber so

    template <typename T>
    class Test 
    {
    public:
        Test(const T&);
    
        template<typename STREAM>
        STREAM& print(STREAM& os) const
        {
            os << value_;
            return os;
        }  
    private:
        T value_;
    };
    
    template <typename T>
    Test<T>::Test(const T& value) : value_(value) { }
    
    template<typename STREAM, typename T>
    STREAM& operator<<(STREAM& os, const Test<T>& rhs)
    {
        return rhs.print(os);
    }
    

    Aber das war ja nicht die Frage...


  • Mod

    Yeequ schrieb:

    Was ist daran verwunderlich? Natürlich kann nicht gelinkt werden, es sind ja zwei unterschiedliche Funktionen. Und für eine gibt es keine Definition, welche aber gebraucht wird. Die andere Funktion ist überflüssig.

    Das ist quasi das gleiche wie hier:

    void print(int);
    void print(char**) { /* print char** */ }
    
    main() {
        print(3);
    }
    

    Das wäre noch treffender, wenn du print(0) geschrieben hättest.
    Die zusätzlich deklarierte (und zudem definierte) Operatorüberladung ist ja durchaus ein Kandidat. Nur ist ein

    template <typename STREAM> STREAM& operator<<(STREAM& os, const Test<T>& rhs);
    

    für beliebige T eben spezieller als

    template<typename STREAM, typename T>
    STREAM& operator<<(STREAM& os, const Test<T>& rhs)
    


  • camper schrieb:

    Yeequ schrieb:

    Was ist daran verwunderlich? Natürlich kann nicht gelinkt werden, es sind ja zwei unterschiedliche Funktionen. Und für eine gibt es keine Definition, welche aber gebraucht wird. Die andere Funktion ist überflüssig.

    Das ist quasi das gleiche wie hier:

    void print(int);
    void print(char**) { /* print char** */ }
    
    main() {
        print(3);
    }
    

    Das wäre noch treffender, wenn du print(0) geschrieben hättest.
    Die zusätzlich deklarierte (und zudem definierte) Operatorüberladung ist ja durchaus ein Kandidat. Nur ist ein

    template <typename STREAM> STREAM& operator<<(STREAM& os, const Test<T>& rhs);
    

    für beliebige T eben spezieller als

    template<typename STREAM, typename T>
    STREAM& operator<<(STREAM& os, const Test<T>& rhs)
    

    Ich bin vor 15 Minuten auch darauf gekommen, daß die Priorisierung der stärker spezialisierten Form innerhalb der Template-Klasse der Grund ist, warum man nur noch alle Spezialisierungen einzeln von Hand angeben kann und das ganze nicht mehr generisch machen kann. An die Priorisierung hatte ich in diesem Fall nicht gedacht. Steht aber genau so unter ISO 14882:2003 Abschnitt 14.5.3.1 beschrieben.



  • Yeequ schrieb:

    Ich bin vor 15 Minuten auch darauf gekommen, daß die Priorisierung der stärker spezialisierten Form innerhalb der Template-Klasse der Grund ist, warum man nur noch alle Spezialisierungen einzeln von Hand angeben kann und das ganze nicht mehr generisch machen kann. An die Priorisierung hatte ich in diesem Fall nicht gedacht. Steht aber genau so unter ISO 14882:2003 Abschnitt 14.5.3.1 beschrieben.

    Okay., es kann an dieser Stelle überhaupt nichts mit der Priorisierung von Templatespezialisierungen zu tun haben. Eine normale Friendfunktion ist an dieser Stelle ja auch kein Template.


  • Mod

    Yeequ schrieb:

    Priorisierung von Templatespezialisierungen

    So etwas gibt es auch nicht, bzw. der Begriff ist so unscharf, dass ich nichts damit anfangen kann.

    Yeequ schrieb:

    Eine normale Friendfunktion ist an dieser Stelle ja auch kein Template.

    Ich kann nicht folgen.



  • camper schrieb:

    Yeequ schrieb:

    Priorisierung von Templatespezialisierungen

    So etwas gibt es auch nicht, bzw. der Begriff ist so unscharf, dass ich nichts damit anfangen kann.

    Yeequ schrieb:

    Eine normale Friendfunktion ist an dieser Stelle ja auch kein Template.

    Ich kann nicht folgen.

    Sorry. Ich habe gerade nicht genau nachgedacht als mir das eingefallen ist. Ich war gerade nebenbei mit etwas anderem beschäftigt. Das tut mir Leid.
    Also es gibt schon eine Priorisierung bei der Templatespezisierung allerdings gibt es Spezialisierung nur für Template-Klassen, nicht für Funktionen:

    template <typename T> class iterator {};
    template <typename T*> class iterator {};
    

    Ich weiß nur halt gerade immer noch nicht genau, warum man zwar die Varianten einzeln definieren kann

    template <typename T>
    struct Foo
    {
        template<typename S>
        friend S& operator<<(S& os, const Foo& rhs);
    };
    template<typename S>
    S& operator<<(S& os, const Foo<int>& rhs) { /* ... */ }
    template<typename S>
    S& operator<<(S& os, const Foo<float>& rhs) { /* ... */ }
    

    Aber eben nicht nochmal irgendwie als Template bzw. warum genau dann stets der Linkage Fehler kommt.

    Mir war nur gerade wieder folgendes in den Sinn gekommen, wie man Template-Funktionen befreundet, die außerhalb der Klasse definiert sind, worauf mir der Gedanke kam, irgendwie könnte das ja vielleicht auch in dem ursprünglichen Fall so ähnlich gehen.

    template <typename T>
    void func0(Test<T> x) {
        std::cout << "func0: " << x.value_ << std::endl;
    }
    
    template <typename T>
    class Test {
        public:
            Test(const T& value) : value_(value) {}
            friend void func0<>(Test);
        private:
            T value_;
    };
    

  • Mod

    Yeequ schrieb:

    Ich weiß nur halt gerade immer noch nicht genau, warum man zwar die Varianten einzeln definieren kann

    template <typename T>
    struct Foo
    {
        template<typename S>
        friend S& operator<<(S& os, const Foo& rhs);
    };
    template<typename S>
    S& operator<<(S& os, const Foo<int>& rhs) { /* ... */ }
    template<typename S>
    S& operator<<(S& os, const Foo<float>& rhs) { /* ... */ }
    

    Aber eben nicht nochmal irgendwie als Template bzw. warum genau dann stets der Linkage Fehler kommt.

    Überleg einfach mal, welches Verhältnis zwischen

    template<typename T> void foo(T, int);
    

    und

    template<typename T, typename U> void foo(T, U);
    

    besteht? Spezialisierung? Überladung? Etwas anderes?



  • camper schrieb:

    Yeequ schrieb:

    Ich weiß nur halt gerade immer noch nicht genau, warum man zwar die Varianten einzeln definieren kann

    template <typename T>
    struct Foo
    {
        template<typename S>
        friend S& operator<<(S& os, const Foo& rhs);
    };
    template<typename S>
    S& operator<<(S& os, const Foo<int>& rhs) { /* ... */ }
    template<typename S>
    S& operator<<(S& os, const Foo<float>& rhs) { /* ... */ }
    

    Aber eben nicht nochmal irgendwie als Template bzw. warum genau dann stets der Linkage Fehler kommt.

    Überleg einfach mal, welches Verhältnis zwischen

    template<typename T> void foo(T, int);
    

    und

    template<typename T, typename U> void foo(T, U);
    

    besteht? Spezialisierung? Überladung? Etwas anderes?

    Also wenn ich diese letzten zwei Funktionen untereinander sehe, dann wird mir auch sofort bewußt, daß die friend-temlate-Deklaration eine Funktion von erstem Muster in die Funktionstabelle einträgt, wohingegen die zweite Funktion zwei Template-Argumente hat. Die Overload Resolution wählt dann natürlich die erste Funktion aus, die aber nicht definiert ist.

    Eigentlich ist es sehr einfach, wenn man seine Gedanken richtig ordnet.


Anmelden zum Antworten