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, wie

    Feld 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 bei

    Feld& operator*=(const Feld<T>& F);
    

    klarerweiser ein anderer Algorithmus zum Einsatz
    kommen muss als bei skalaren Typen wie

    Feld& 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 Member operator*= 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 Feld eine 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 Member operator*= 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 Feld eine 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;
    }
    

Anmelden zum Antworten