Template Spezialisierung/Instantiierung
-
Hallo,
ich will Arithmetik mit Sequenzen (DSP, programmtechnisch ähnlich Array)
möglichst natürlich (mathematisch üblich) schreiben können.
Dazu werden Operatoren wie *= entsprechend überladen.Problem: dabei kommt es zu recht ungewöhnlichen Typmixturen.
Beispiel:Feld& Feld::operator*=(Feld& F);Klare Sache. Aber es muss eben auch
Feld& Feld::operator*=(double& f);gehen (Skalierung eines Vektors oder Feldes mit Skalar).
Neben double kann auch float, int usw. auftreten.
Auch Nichtmitglieder der Klasse gibt es, wieFeld operator*(Feld& F,double d); Feld operator*(double d, Feld& F);wieder statt double auch andere skalare Typen.
Das alles, damit man natürlich schreiben kann:
F = 3.0*A; //literal implizit doubke F = A*2; // literal int F = 2.0*A + 3.0*B;Solange das nicht templatisiert ist, ist der Compiler
noch gnädig. Wenn man das templatisiert, muss der Argumenttyp
exakt stimmen, d.h. z.B. der zweite Ausdruck wird nicht mehr
akzeptiert, denn wenn nur ein Template mit double angeboten wird,
wird keine Konvertierung von int nach double gemacht, wie
wenn kein Template zum Einsatz kommt. Wohlgemerkt: der
Template Typ T ist kein Skalar, er bezeichnet den Typ der
Sequenz.
Ich habe probiert, zwei Template Typen einzuführen, T für
Sequenztyp und D für die skalaren Typen. Aber ich kriege
den Compiler nicht dazu, alle benötigten Funktionen anzulegen.
Was funktioniert ist, den Sequenztyp zu templatisieren,
aber den Skalartyp jedesmal hinzuschreiben, also:Field<T>& Field<T>::operator*=(double& d); Field<T>& Field<T>::operator*=(float& d); Field<T>& Field<T>::operator*=(int& d); Field<T>& Field<T>::operator*=(Feld<T>& F);Für die Nichtmitglieder gibt es noch mehr Varianten. Das
ist äußerst lästig.Wie templatisiert man so einen Typmix aus elementaren
skalaren Typen und selbstgebauten Klassen? Alle Texte,
die ich konsultiert habe, machen eine solche Mischung
nicht.MfG
Wastl
-
Ich versteh nicht ganz,
Feld operator*(Feld& F,double d);sollte doch mit float, int, etc. funktionieren!? Ansonsten eben
template <typename D> Feld operator*(Feld& F, D d);Oder willst du dass dein Feld autom. umgewandelt wird? Prinzipiell ist es von Vorteil solche Operatoren als freie Funktionen und nicht als Memberfunktionen anzulegen da dann für beide Parameter des Ausdrucks Konvertierungen angewandt werden können.
-
Sagte ich doch, dass das noch funktioniert. In dem zuerst von Dir genannten Beispiel ist noch nichts templatisiert. Wie ich sage funktioniert das noch, weil der Compilier dann noch implizite Typkonvertierungen für eingebaute Skalare vornimmt und ja noch keine Funktionen aus Templates erzeugt werden müssen.
Sobald die Sache aber templatisiert ist, ist damit Schluss. Dein Beispiel ist templatisiert, aber nicht das, was ich möchte. Bei mit soll das Feld templatisiert sein, die Skalare dann irgendwie dazu. Bei Dir ist es genau umgekehrt.template<T> class Feld { ... Feld(int n); ... Feld& operator*=(const Feld<T>& F); Feld& operator*=(const double& d); Feld& operator*=(const float& d); Feld& operator*=(const long double& d); Feld& operator*=(const int& d); Feld& operator*=(const uint32& d); ... };Wenn ich die verschiedenen Sklartypen nicht explizit
ausschreibe, also vorsehe, dann kann der Linker die
hinterher nicht finden, wenn sie gebraucht werden.
Die Sache wird dadurch noch interessanter, als beiFeld& operator*=(const Feld<T>& F);klarerweiser ein anderer Algorithmus zum Einsatz
kommen muss als bei skalaren Typen wieFeld& operator*=(const double& d);Der Grundtyp von Feld T kann sein:
Real: float, double, long double.
Komplex: dito.Wastl
-
D.h. du willst eigentlich sowas:
template <typename U, typename V> Feld<U> operator *(Feld<U>& F, V d);!? Ich versteh immer noch nicht wirklich was jetzt genau dein Problem ist

-
ja, so ein Template mit 2 Parametern habe ich versucht.
Erfolglos. Tschuldigung, ich habe meine Anforderung oben
noch einmal weiter präzisiert.
Problematisch ist, dass ich zwei Algorithmen verwenden muss,
einen für Skalare und einen für Felder.
Templates machen aber immer dasselbe, setzen nur andere
Werte ein.
-
wastl schrieb:
ja, so ein Template mit 2 Parametern habe ich versucht.
Erfolglos.Und "erfolglos" bedeutet genau was?
-
1. Was ist das Problem an der Konvertierung nach double?
2. Es geht, Feld als Feld-Typ zu interpretieren und alles andere als Zahlen. Das sähe dann so aus:template<typename T> operator*(Feld const &first, T const &second); template<typename T> operator*(T const &first, Feld const &second); Feld &Feld::operator*(Feld const &first, Feld const &second);3. Du kannst natürlich auch noch den skalaren Typ, der intern von Feld verwendet wird, templatisieren. Willst du das?
4. Du kannst auch mit einem komplizierten Verfahren den übergebenen Typ bei deinem Mal-Operator unterschiedlich behandeln, abhängig davon, ob er eine isfield()-Methode besitzt oder nicht usw...Mach doch mal ein Anwenderbeispiel, damit wir verstehen, was du genau brauchst.
-
Deklariere und definiere operator*(const Feld F<T>&, double skalar<) als friend von der Templateklasse Feld und innerhalb dieser.
Etwa so:
template <typename T> class Feld { typedef Feld self; public: //... friend self operator*(const self& F, double skalar) { self result(F); for(int i = 0; i < F.size(); ++i) result[i] *= skalar; return result; } //... };ps: wobei ich für eine Skalarmultiplikation nur den operator*(double, const Feld&); anbieten würde. Andersrum sieht das komisch aus.
-
bmario schrieb:
Deklariere und definiere operator*(const Feld F<T>&, double skalar<) als friend von der Templateklasse Feld und innerhalb dieser.
Nein. Lass die globalen
operator*auf den Memberoperator*=zurückgreifen.Feld& Feld::operator*= (double rhs); Feld operator* (Feld lhs, double rhs); Feld operator* (double lhs, Feld rhs);bmario schrieb:
ps: wobei ich für eine Skalarmultiplikation nur den operator*(double, const Feld&); anbieten würde. Andersrum sieht das komisch aus.
"Komisch aussehen" ist kein Grund, die Kommutativität der Multiplikation (die zumindest hier vorhanden ist, falls
Feldeine Art Vektor ist) zu verletzen.
-
Nexus schrieb:
bmario schrieb:
Deklariere und definiere operator*(const Feld F<T>&, double skalar<) als friend von der Templateklasse Feld und innerhalb dieser.
Nein. Lass die globalen
operator*auf den Memberoperator*=zurückgreifen.Feld& Feld::operator*= (double rhs); Feld operator* (Feld lhs, double rhs); Feld operator* (double lhs, Feld rhs);Nexus schrieb:
bmario schrieb:
ps: wobei ich für eine Skalarmultiplikation nur den operator*(double, const Feld&); anbieten würde. Andersrum sieht das komisch aus.
"Komisch aussehen" ist kein Grund, die Kommutativität der Multiplikation (die zumindest hier vorhanden ist, falls
Feldeine Art Vektor ist) zu verletzen.Mit "komisch aussehen" meinte ich, dass die Skalarmultiplikation eines Vektorraumes im Allgemeinen eben nicht kommutativ ist. Allerdings gibt es Spezialfälle, für welche dies gilt, z.B. für den R-Vektorraum R^1.
-
der Ärger geht schon mit Literalen los. Literale werden
offenbar bei der Template-Instantiation anders behandelt
als Variablen. Z.B.template<class T, class D> feld<T> operator+(const feld<T>& Lhs,const D& rhs); template<class T, class D> feld<T> operator+(const feld<T>& Lhs,const D& rhs) { feld<T> Tmp; Tmp += Lhs; Tmp += rhs; return Tmp; } ... int main() { feld<float> A(10); int i=0; A+i; // geht, Variable, Funktionsinstantiation wird angelegt A+3.0; // geht nicht, Literal A+1; // geht nicht, Literal }Oberstes Ziel für mich ist, dass Ausdrücke natürlich geschrieben
werden können. Es reichst schon, dass die Mathematik kompliziert
ist, da brauche ich nicht noch eine nicht intuitive Syntax.Ich werde wohl Templates für den Feld-Typ verwenden, aber
für die skalaren Typen das Gemeinsame in eine Unterfunktion
packen und das Besondere vielleicht per Perl-Script aus
einem Prototypen auswalzen.Wenn die Tyen, die überhaupt benötigt werden, sehr überschaubar
sind und wenn sehr verschiedene Algorithmen je Typ
angewendet werden, dann verlieren Templates sehr stark an
Attraktivität. Für die Container der STL funktioniert das,
aber z. B. für eine FFT, die je nach Argumenttyp optimierte
Algorithmen loslässt, ist das eben nicht so.
Übrigens: wie kann eine Funktion den Typ des Arguments erkennen?
Wastl
-
,D const& rhs)Sonst klappt's nicht bei r-values.
-
@wastl: Eine Funktion "erkennt" den Typ des Templates nicht, für jeden Typ wird eine eigene Funktion vom Compiler generiert, deswegen muss der Typ auch zur Compilezeit bekannt sein. Übrigens klingt das, was du da sagst, einerseits unverständlich und andererseits so, als ob man dir durchaus noch helfen könnte, deshalb bitte ich dich nochmal, das Zeug mal genau zu erklären. Ein Funktionsbeispiel mit allen Features hast du auch noch nicht gemacht.
-
im letzten Beispiel habe ich ein Beispiel
für den Operator "+" angegeben.
Der Compiler bildet die richtige Funktion
aus dem Template, bzw. casted, wenn die Argumente
Variablen sind. Bei Literalen funktioniert
das Beispiel nicht. Also habe ich doch
ein Beispiel gegeben. (die const habe ich gefixt).Arithmetische Operationen enthalten i.d.R.
Literale, so dass eine natürliche Schreibweise
dadurch nicht möglich ist.Das ist schon ein nicht unerhebliches Teilproblem
des Ganzen und sollte zunächst betrachtet werden.I.d.R. werden immer Templates der Sorte
template<T> Feld<T>& Feld<T>::operator+=(Feld<T>& Rhs);betrachtet, für die gar kein Problem auftritt, weil
der Typ von Rhs mit dem Feldtyp übereinstimmt.
Wenn die Situation asymmetrisch wird, beginnen die
Probleme:template<T> Feld<T>& Feld<T>::operator+=(TYPE& Rhs);wobei Type irgendein skalarer Typ ist, eine Lösung
ist gesucht, die solche Skalaren Typen allesamt
abdeckt und auch mit Literalen zurecht kommt.
Dito für Nichtmitgliedfunktionen.
-
Am besten du machst die Übergabe immer da, wo es sinnvoll ist, als const-Referenz.
Auf Literale und temporäre Ausdrücke können keine veränderlichen Referenzen genommen werden.z.B.
class Vector; const Vector operator+(Vector& lhs, Vector& rhs); Vector v,w,z; //... Vector sum=v+w+z; //Fehler, es wird eine Referenz auf ein temporäres Objekt erzeugt Vector sum2 = sum + 2; //Fehler, es wird eine Referenz auf ein Literal erzeugt
-
Das mit den const ist schon klar, bloß hier
gespart beim Tippen.Ich habe das allgemein jetzt so gelöst:
template<T> class Feld { public: Feld(int n); ~Feld(); Feld& operator+=(const Feld& Rhs); // das ist der Normalfall, selber Typ Feld& operator+=(const double& rhs); // damit gehen Skalare und auch Literale Feld& operator+=(const float& rhs); // damit gehen Skalare Feld& operator+=(const int& rhs); // damit gehen Skalare und auch Literale Feld& operator+=(const uint32& rhs); // damit gehen Skalare ... };Für die Nichtmitglieder entsprechend, da gibt es noch mehr Varianten:
Feld<T> operator+(const Feld<T>& Lhs, const Feld<T>& Rhs); Feld<T> operator+(const Feld<T>& Lhs, const double& rhs); Feld<T> operator+(const Feld<T>& Lhs, const float& rhs); Feld<T> operator+(const Feld<T>& Lhs, const int& rhs); Feld<T> operator+(const Feld<T>& Lhs, const uint32& rhs); Feld<T> operator+(const double& lhs, const Feld<T>& Rhs); Feld<T> operator+(const float& lhs, const Feld<T>& Rhs); Feld<T> operator+(const int& lhs, const Feld<T>& Rhs); Feld<T> operator+(const uint32& lhs, const Feld<T>& Rhs);Schön ist auch "-" oder "/", weil nicht kommutativ, also dann
3 Algorithmen: für Feld<T>, für Skalar links und Skalar rechts.
Na und wenn T komplexe Zahlen repräsentiert, dann machen
viele Kombinationen wenig Sinn. Spezialisierung zur
Elimination solcher Varianten ??Ich präzisiere meine Frage jetzt: wie kann ich die mehrfache
Anlage für jeden Operator sparen? Dass es für einen Vektor
anders ist als für einen Skalar, das ist ja klar, anderer Code.
Aber wenigstens die skalaren Typen sollte man doch irgendwie
zusammen erschlagen können.template<T,D>Konstruktionen helfen nicht bei Literalen und auch sonst,
auch kam es zum Überladen von Operatoren per Mehrfachinstantiation
durch den Compiler. Das habe ich schon probiert.
Das aus-x-en wie oben funktioniert, das weiß ich sicher.Man muss es probieren, denn vieles funktioniert in der
Praxis doch nicht, z. B. wegen der impliziten Template-Auswerte-Regeln.
-
Ich kann dein Problem nicht nachvollziehen?
#include <iostream> #include <vector> #include <cassert> template <typename T> class vector { public: explicit vector(int size) : data(size) {} const T& operator[](int n) const { return data[n]; } T& operator[](int n) { return data[n]; } int num_rows() const { return data.size(); } friend const vector<T> operator+(const vector<T>& lhs, const vector<T>& rhs) { assert(lhs.num_rows() == rhs.num_rows()); vector<T> result(lhs.num_rows()); for (int i=0; i<lhs.num_rows(); i++) { result[i] = lhs[i] + rhs[i]; } return result; } template <typename D> friend const vector<T> operator+(const vector<T>& lhs, const D& rhs) { vector<T> result(lhs.num_rows()); for (int i=0; i<lhs.num_rows(); i++) { result[i] = lhs[i] + rhs; } return result; } template <typename D> friend const vector<T> operator+(const D& lhs, const vector<T>& rhs) { vector<T> result(rhs.num_rows()); for (int i=0; i<rhs.num_rows(); i++) { result[i] = lhs + rhs[i]; } return result; } private: vector(); std::vector<T> data; }; template <typename T> std::ostream& operator<<(std::ostream& s, const vector<T>& v) { s << "[ "; for (int i=0; i<v.num_rows()-1; i++) { s << v[i] << ", "; } s << v[v.num_rows()-1] << "]\n"; return s; } int main (int argc, char * const argv[]) { vector<double> a(3),b(3); a[0] = 5; a[2] = 7; b[1] = 3; std::cout << a << b; std::cout << a+b << a+5 << 5+a << 2.5 + a + 2.5 << a+5.0 << 0. + a + 0L + 5U; return 0; }