Wie trennt man Deklaration und Definition von friend-member-template-Funktionen?
-
brotbernd schrieb:
template <typename T> class Test { public: template <typename STREAM> friend STREAM& operator<<(STREAM& os, const Test<T>& rhs); T value_; }; template<typename STREAM, typename T> STREAM& operator<<(STREAM& os, const Test<T>& rhs) { os << rhs.value_; return os; }Erstens kompiliert das ganze leider nicht und zweitens ist es ganz was anderes. Zudem weicht es die Zugriffsbeschränkung auf und operator<<() ist hier eine freie Funktion.
-
Beim OP war es auch schon eine freie Funktion, nur dass die eben inline definiert war. Das Problem an brotbernds Lösung ist, dass es zwar kompiliert, aber der Linker das dann nicht auflösen kann, weil die friend-Deklaration leicht unterschiedlich ist zur eigentlichen Funktion. Richtig heißt es so:
#include <iostream> template <typename T> class Test { public: Test(const T&); template<typename _T, typename STREAM> friend STREAM& operator<<(STREAM& os, const Test<_T>& rhs); private: T value_; }; template<typename T, typename STREAM> STREAM& operator<<(STREAM& os, const Test<T>& rhs) { os << rhs.value_; return os; } template <typename T> Test<T>::Test(const T& value) : value_(value) { } int main() { std::cout << Test<int>(1) << std::endl; std::cout << Test<double>(2.2) << std::endl; }
-
Ulrich_B schrieb:
Beim OP war es auch schon eine freie Funktion, nur dass die eben inline definiert war. Das Problem an brotbernds Lösung ist, dass es zwar kompiliert, aber der Linker das dann nicht auflösen kann, weil die friend-Deklaration leicht unterschiedlich ist zur eigentlichen Funktion. Richtig heißt es so:
#include <iostream> template <typename T> class Test { public: Test(const T&); template<typename _T, typename STREAM> friend STREAM& operator<<(STREAM& os, const Test<_T>& rhs); private: T value_; }; template<typename T, typename STREAM> STREAM& operator<<(STREAM& os, const Test<T>& rhs) { os << rhs.value_; return os; } template <typename T> Test<T>::Test(const T& value) : value_(value) { } int main() { std::cout << Test<int>(1) << std::endl; std::cout << Test<double>(2.2) << std::endl; }Im Original ist die Funktion nicht frei sondern an die Klasse gebunden und wird erst mit der Instantiierung der Klasse sichtbar. In der vorgeschlagenen Lösung ist die Funktion eine globale Funktion.
Zudem ist ja wie schon bemerkt das Templateargument ein anderes. Meine Frage war halt genau, wie man diese ursprüngliche member-Funktion abtrennt bzw. warum dies nicht gehen sollte. Nicht eine ähnliche Funktion erstellt, die die gleiche Ausgabe produziert.
-
Als Beispiel: Lasse ich die Definition der Funktion weg, dann kann nicht gelinkt werden, da die Funktion nicht definiert ist.
// weiterer quellcode ... template <typename T> class Test { public: Test(const T&); template <typename STREAM> friend STREAM& operator<<(STREAM& os, const Test<T>& rhs); private: T value_; }; // weiterer quellcode ...Wie genau erstellt man also die Definition genau der Funktion, welche der Linker an der Stellt vermisst?
-
Yeequ schrieb:
Meine Frage war halt genau, wie man diese ursprüngliche member-Funktion abtrennt bzw. warum dies nicht gehen sollte.
Das ist keine Member-Funktion. War nie eine, wird nie eine. Der Streamausgabeoperator kann gar keine Memberfunktion sein.
-
MFK schrieb:
Yeequ schrieb:
Meine Frage war halt genau, wie man diese ursprüngliche member-Funktion abtrennt bzw. warum dies nicht gehen sollte.
Das ist keine Member-Funktion. War nie eine, wird nie eine. Der Streamausgabeoperator kann gar keine Memberfunktion sein.
Egal ob es eine Member-Funktion ist oder nicht, wie erreiche ich es dennoch, die Definition der Funktion
template <typename STREAM> friend STREAM& operator<<(STREAM& os, const Test<T>& rhs);zu erstellen ohne einen anderen Template Parameter einzuführen. Nimmt man einen anderen Template Parameter, ist es ja eine andere Funktion.
-
Es ist dann nicht zwangsläufig eine andere Funktion, da es auf die konkreten Typen der Template-Argumente ankommt, was das mal für eine Funktion wird. Allerdings kann ich dir auch nicht beantworten, wie man das syntaktisch richtig ausformuliert, ohne einen zweiten Template-Parameter hinzuzufügen. Vom Ergebnis her macht das IMHO aber keinen Unterschied, weder von der Typsicherheit her noch vom Kompilat.
-
template <typename STREAM> friend STREAM& operator<<(STREAM& os, const Test<T>& rhs);Du deklarierst hier eine Familie von << operatoren als
friendder 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.
-
Ulrich_B schrieb:
Es ist dann nicht zwangsläufig eine andere Funktion, da es auf die konkreten Typen der Template-Argumente ankommt, was das mal für eine Funktion wird. Allerdings kann ich dir auch nicht beantworten, wie man das syntaktisch richtig ausformuliert, ohne einen zweiten Template-Parameter hinzuzufügen. Vom Ergebnis her macht das IMHO aber keinen Unterschied, weder von der Typsicherheit her noch vom Kompilat.
Das es den Umweg über den zweiten Template Parameter gibt wußte ich vorher schon. Und es ist auch richtig, daß das Result ziemlich gleich ist und für den täglichen Gebrauch egal.
Nur der Kompiler legt ja in seiner Funktionstabelle unterschiedliche Funktionsnamen an und ich frage mich halt, wie man für die Funktion, welche dort angelegt wird, die Definition auslagern kann. Bei allen anderen Funktionen ist es ja auch möglich. Und ich würde schon gerne wissen, wie es geht bzw. warum es nicht gehen sollte. Eine Antwort wie "der Kompiler sagt nein, also mach ich es anders" ist nun mal keine befriedigende Antwort.
-
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.
-
brotbernd schrieb:
template <typename STREAM> friend STREAM& operator<<(STREAM& os, const Test<T>& rhs);Du deklarierst hier eine Familie von << operatoren als
friendder 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
friendder 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
friendder 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...
-
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 eintemplate <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 eintemplate <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.
-
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.