Funktion mit Template als Argument



  • Hallo,

    ich möchte mir eine Vektor-Klasse (3D) als Template bauen und habe das Problem eine Funktion zu schreiben, welche zwei Objekte meiner Template-Klasse als Argumente übergeben bekommt. Gefühlt "quick & dirty" habe ich es hinbekommen, aber ich denke, dass es sicher nicht der Weisheit letzter Schluss ist... Der ganze Code befindet sich in einer einzigen Header-Datei.
    Die (gekürzte) Template-Klasse lautet:

    template <class Type>
    class Vector {
    
    public:
    
        Vector();
        Vector(Type x=0, Type y=0, Type z=0);
        ~Vector();
    
        // Operations
        friend Type dotProduct(const Vector<Type>& a, const Vector<Type>& b);
    
    private:
        Type x;
        Type y;
        Type z;
    
    };
    

    Ich möchte u.a. die folgende Funktion implementieren:

    template <class Type> // ist das hier nötig? 
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b);
    

    und wie folgt verwenden (für int, long, float und double):

    int main()
    {
        Vector<int> int_vec1(1,2,3);
        Vector<int> int_vec2(3,3,3);
    
        std::cout<< dotProduct(float_vec1,float_vec2) <<std::endl;
    
    return 0;
    

    Zum laufen kriege ich das Prog. mit:

    int dotProduct(const Vector<int>& a, const Vector<int>& b) {
        return (a.x*b.x + a.y*b.y + a.z*b.z);
    }
    
    long dotProduct(const Vector<long>& a, const Vector<long>& b) {
        return (a.x*b.x + a.y*b.y + a.z*b.z);
    }
    
    float dotProduct(const Vector<float>& a, const Vector<float>& b) {
        return (a.x*b.x + a.y*b.y + a.z*b.z);
    }
    
    double dotProduct(const Vector<double>& a, const Vector<double>& b) {
        return (a.x*b.x + a.y*b.y + a.z*b.z);
    }
    

    Kurz, ich habe zwei Fragen:

    1. Wie kann ich die Geschichte vernünftig schreiben, ohne für jeden Datentyp eine Spezialisierung zu formulieren?
    2. Kann ich eine Art "Positivliste" für meine Template-Klasse formulieren, sodass ich nur beispielsweise int und float als Datentypen für den Vector angeben kann, andere Datentypen aber Fehler ausgeben?

    Vielen Dank im Voraus!



  • Was spricht gegen den Ansatz mit template <class Type> für dotProduct?
    Und was das zweite angeht: Mit C++11 kannste

    template <class Type>
    class vector
    {
      static_assert(std::is_arithmetic<Type>::value, "Type muss arithemtischer Typ sein (int, float, etc.)!");
      ...
    };
    

    schreiben (Header type_traits erforderlich).
    Ohne C++11 musst du std::is_arithmetic und std::enable_if nachbauen und dann:

    template <class Type, typename IsArithmetic = void>
    class vector;
    
    template <class Type>
    class vector<Type, typename std::enable_if<std::is_arithmetic<Type>::value>::type>
    {
       // deine Funktionen etc.
    };
    


  • Nathan schrieb:

    Was spricht gegen den Ansatz mit template <class Type> für dotProduct?

    Ich hatte gehofft, dass ich den Code nicht viermal hinterlegen muss mit quasi demselben Inhalt und es auf eine Funktionsimplementation eindampfen könnte.

    zu 2) Vielen Dank für den Tipp! Wieder was gelernt 🙂



  • chmbw schrieb:

    Nathan schrieb:

    Was spricht gegen den Ansatz mit template <class Type> für dotProduct?

    Ich hatte gehofft, dass ich den Code nicht viermal hinterlegen muss mit quasi demselben Inhalt und es auf eine Funktionsimplementation eindampfen könnte.

    Ja, musst du auch nicht:

    template <class Type>
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b)
    {
      return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    


  • Das hatte ich probiert und bin immer bei folgendem Fehler gelandet:

    error: undefined reference to `dotProduct(Vector<float> const&, Vector<float> const&)'

    #include "vector.h"
    
    int main()
    {
    
        Vector<float> float_vec1(2.5f, 2.5f, 5.0f);
        Vector<float> float_vec2(1.0f, 1.0f, 1.0f);
        std::cout<<dotProduct(float_vec1,float_vec2) <<std::endl;
    
        return 0;
    }
    

    Den Grund verstehe ich nicht wirklich und habe deshalb den anderen Ansatz gewählt...



  • Du hast deine Friend-Funktion falsch deklariert:

    template <typename Type> 
    friend Type dotProduct(const Vector<Type>& a, const Vector<Type>& b);
    


  • Danke für den Hinweis!
    Ich habe jetzt folgendes:

    template <class Type>
    class Vector {
    
    public:
    
        Vector(Type x, Type y, Type z);
    
        template <typename T> friend T dotProduct(const Vector<T>& a, const Vector<T>& b);
    
    private:
        Type x;
        Type y;
        Type z;
    
    };
    
    template <class Type>
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b)
    {
      return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    

    Ich habe die friend-function mit einem Anderen Template-Bezeichner als friend in der Klasse angegeben, da ich sonst einen shadowing - error bekommen habe. So wie es jetzt angegeben ist, funktioniert es auch wunderbar.



  • Schön 🙂
    Ich hab aber auch noch eine Frage an alle:

    Wieso beschwert sich der Linker über eine fehlende Funktion und nicht der Kompiler (der ja die freie Funktion finden sollte) über den private-Zugriff?

    ---------

    Ok, hab die Antwort gefunden:
    Stackoverflow



  • Jockelx schrieb:

    Schön 🙂
    Ich hab aber auch noch eine Frage an alle:

    Wieso beschwert sich der Linker über eine fehlende Funktion und nicht der Kompiler (der ja die freie Funktion finden sollte) über den private-Zugriff?

    ---------

    Ok, hab die Antwort gefunden:
    Stackoverflow

    Danke für den Hinweis! 🙂



  • Hallo,

    sehe ich das richtig, dass du damit im Wesentlichen das inner_product nachgebaut hast?

    Gruß,
    -- Klaus.



  • Klaus82 schrieb:

    Hallo,

    sehe ich das richtig, dass du damit im Wesentlichen das inner_product nachgebaut hast?

    Gruß,
    -- Klaus.

    inneres produkt == skalarprodukt == dot product. also hast du recht, ja.



  • Klaus82 schrieb:

    Hallo,

    sehe ich das richtig, dass du damit im Wesentlichen das inner_product nachgebaut hast?

    Gruß,
    -- Klaus.

    Ja, habe ich. Es sollte aber erstmal exemplarischer Natur sein, ich habe mir noch einge andere Funktionen wie Dyadisches- und Kreuzprodukt usw. gebastelt. Zudem ging es mir um eine Möglichkeit mich näher mit Templates zu befassen. Trotzdem danke für den Hinweis auf diese nette Funktion! 🙂



  • chmbw schrieb:

    Danke für den Hinweis!
    Ich habe jetzt folgendes:

    template <class Type>
    class Vector {
    
    public:
    
        Vector(Type x, Type y, Type z);
    
        template <typename T> friend T dotProduct(const Vector<T>& a, const Vector<T>& b);
    
    private:
        Type x;
        Type y;
        Type z;
    
    };
    
    template <class Type>
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b)
    {
      return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    

    Kleiner Tipp: Wenn du so ein "Muster" wie oben hast, dann mach dotProduct zu einer wirklich freien Funktion außerhalb und spendiere Vector eine Memberfunktion dotProduct:

    Type Vector::dotProduct(const Vector<Type>& b) const
    {
        return x * b.x + y * b.y + z * b.z; 
    }
    
    template <class Type> 
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b) 
    { 
      return a.dotProduct(b); 
    }
    

    Jetzt bist du erstmal friend los und hast noch ein paar weitere Vorteile. Wenn du mal zB von Vector eine weitere Klasse ableiten solltest, zB Vector4, dann kannst du diese freie Funktion immer noch verwenden. Ansonsten müsstest du für jede Hirarchie-Ebene eine weitere Funktion außerhalb implementieren. So ist Implementierung dann auch dort, wo sie von der Zuständigkeit her hingehört. In deinem VectorBase packst du dann noch ein virtual Type dotProduct() = 0; rein und alle nachfolgenden Klassen freuen sich auch. Mitunter hast du jetzt die Kapselung erhöht, da weniger Funktion von außen auf deinem Member zugreifen können und kannst das Programm auch schneller und wartungsfreier erweitern.



  • um friend loszuwerden müsste ich dann ja

    Type Vector::dotProduct(const Vector<Type>& b) const
    {
        return x * b.x + y * b.y + z * b.z;
    }
    

    als public deklarieren. Wenn ich jetzt allerdings nur die externe Funktion

    template <class Type>
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b)
    {
      return a.dotProduct(b);
    }
    

    zulassen möchte, muss ich diese doch wieder als friend in der Vektorklasse angeben, oder nicht?!



  • KasF schrieb:

    Kleiner Tipp: Wenn du so ein "Muster" wie oben hast, dann mach dotProduct zu einer wirklich freien Funktion außerhalb und spendiere Vector eine Memberfunktion dotProduct:

    Type Vector::dotProduct(const Vector<Type>& b) const
    {
        return x * b.x + y * b.y + z * b.z; 
    }
    
    template <class Type> 
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b) 
    { 
      return a.dotProduct(b); 
    }
    

    Schwachsinn.
    Wo ist Dein Muster nochmal passend? Beim operator<<(ostream&,Foo) zusammen mit virtual Foo::printAt(ostream&). Das heißt aber noch lange nicht, daß Dein Muster auf jedes Problem passt.

    KasF schrieb:

    Jetzt bist du erstmal friend los und hast noch ein paar weitere Vorteile.

    Jo, friend ist weg, und die Schnittstelle ist nicht mehr minimal, die Methode muss wieder private werden und das friend muss wieder hin. Und dotProduct hat auf einmal ein this, das Subjekt, den Täter, was nicht gut passt, diese Lüge muss aus der Schnittstelle raus. Deswegen kann man in http://www.c-plusplus.net/forum/324787 sich nicht mit 45 Minuten beschränken.

    KasF schrieb:

    Wenn du mal zB von Vector eine weitere Klasse ableiten solltest, zB Vector4, dann kannst du diese freie Funktion immer noch verwenden.

    Achso! Ein Vector4 IST EIN Vector3 mit einem Element mehr. Die void retteWelt(Vector3 const&) kann ich beruhigt mit einem Vector4 aufrufen, wenn ich gerade keinen Vector3 zur Hand habe. Schwachsinn. "Herr Bebel…" lesen und nicht den Wolf.

    KasF schrieb:

    Ansonsten müsstest du für jede Hirarchie-Ebene eine weitere Funktion außerhalb implementieren.

    Ex falso quod libet, woraus ich zu schließen beliebe, daß man für jede weitere Hierarchie-Ebene eben keine weitere Funktion außerhalb definieren muss.

    KasF schrieb:

    So ist Implementierung dann auch dort, wo sie von der Zuständigkeit her hingehört.

    Nein. Was Templates getrennt haben, soll die Vererbung nicht zusammenführen.

    KasF schrieb:

    In deinem VectorBase packst du dann noch ein virtual Type dotProduct() = 0; rein und alle nachfolgenden Klassen freuen sich auch.

    Ach nöö, das ist doch kein Java-Kindergarten hier.

    KasF schrieb:

    Mitunter hast du jetzt die Kapselung erhöht, da weniger Funktion von außen auf deinem Member zugreifen können und kannst das Programm auch schneller und wartungsfreier erweitern.

    Ganz sicher hast Du mit dem sinnlosvererben die Abhängigkeiten erhöht und Dir eine Warungsbremse, wenn nicht gar eine Wartungsmauer gebaut. Wenn TyRoXx sowas jeden Tag sehen müßte, könnte ich seine generellen Ressentiments gegen Vererbung fast verstehen.



  • volkard schrieb:

    KasF schrieb:

    Kleiner Tipp: Wenn du so ein "Muster" wie oben hast, dann mach dotProduct zu einer wirklich freien Funktion außerhalb und spendiere Vector eine Memberfunktion dotProduct:

    Type Vector::dotProduct(const Vector<Type>& b) const
    {
        return x * b.x + y * b.y + z * b.z; 
    }
    
    template <class Type> 
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b) 
    { 
      return a.dotProduct(b); 
    }
    

    Schwachsinn.
    Wo ist Dein Muster nochmal passend? Beim operator<<(ostream&,Foo) zusammen mit virtual Foo::printAt(ostream&). Das heißt aber noch lange nicht, daß Dein Muster auf jedes Problem passt.

    KasF schrieb:

    Jetzt bist du erstmal friend los und hast noch ein paar weitere Vorteile.

    Jo, friend ist weg, und die Schnittstelle ist nicht mehr minimal, die Methode muss wieder private werden und das friend muss wieder hin. Und dotProduct hat auf einmal ein this, das Subjekt, den Täter, was nicht gut passt, diese Lüge muss aus der Schnittstelle raus. Deswegen kann man in http://www.c-plusplus.net/forum/324787 sich nicht mit 45 Minuten beschränken.

    KasF schrieb:

    Wenn du mal zB von Vector eine weitere Klasse ableiten solltest, zB Vector4, dann kannst du diese freie Funktion immer noch verwenden.

    Achso! Ein Vector4 IST EIN Vector3 mit einem Element mehr. Die void retteWelt(Vector3 const&) kann ich beruhigt mit einem Vector4 aufrufen, wenn ich gerade keinen Vector3 zur Hand habe. Schwachsinn. "Herr Bebel…" lesen und nicht den Wolf.

    KasF schrieb:

    Ansonsten müsstest du für jede Hirarchie-Ebene eine weitere Funktion außerhalb implementieren.

    Ex falso quod libet, woraus ich zu schließen beliebe, daß man für jede weitere Hierarchie-Ebene eben keine weitere Funktion außerhalb definieren muss.

    KasF schrieb:

    So ist Implementierung dann auch dort, wo sie von der Zuständigkeit her hingehört.

    Nein. Was Templates getrennt haben, soll die Vererbung nicht zusammenführen.

    KasF schrieb:

    In deinem VectorBase packst du dann noch ein virtual Type dotProduct() = 0; rein und alle nachfolgenden Klassen freuen sich auch.

    Ach nöö, das ist doch kein Java-Kindergarten hier.

    KasF schrieb:

    Mitunter hast du jetzt die Kapselung erhöht, da weniger Funktion von außen auf deinem Member zugreifen können und kannst das Programm auch schneller und wartungsfreier erweitern.

    Ganz sicher hast Du mit dem sinnlosvererben die Abhängigkeiten erhöht und Dir eine Warungsbremse, wenn nicht gar eine Wartungsmauer gebaut. Wenn TyRoXx sowas jeden Tag sehen müßte, könnte ich seine generellen Ressentiments gegen Vererbung fast verstehen.

    Danke für deinen Beitrag, wie würdest du dann mein "Problem" lösen? So wie ich es vorher hatte mit:

    template <class Type>
    class Vector {
    
    public:
    
        Vector(Type x, Type y, Type z);
    
        template <typename T> friend T dotProduct(const Vector<T>& a, const Vector<T>& b);
    
    private:
        Type x;
        Type y;
        Type z;
    
    };
    
    template <class Type>
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b)
    {
      return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    

    Danke im Voraus!



  • Mach x, y und z einfach public .
    Ansonsten kannst du versuchen mir zu erklären warum x, y und z eines Vektors private sein sollten.



  • mir ist suspekt wieso es keine getter für die vektor-komponenten gibt.

    ich nutze die gelegenheit um aus eigenem interesse nachzufragen:
    vec2 / vec3 haben mit vec4 doch eigentlich nicht viel am hut, da das w-element aus vec4 nicht als neue komponente behandelt wird (also nicht so wie z bei vec2 -> vec3) weil es sich um homogene koordinaten handelt. sehe ich das richtig? daher finde ich allgemein den ansatz, vec als template<typename T, std::size_t n> zu implementieren, ein wenig überdenkenswert. ist meine überlegung korrekt / gerechtfertigt?

    ich hab das problem, dass ich mir bei solchen modellierungsproblemen zu viele gedanken mache und dass mich nicht auf das wesentliche konzentrieren kann.

    edit: wenn vec4 und vec3 nicht durch dieses klassentemplate (oben angedeutet) instanziiert werden, dann ist die frage, wie man das ganze möglichst generisch und ohne code-overhead realisieren kann.



  • chmbw schrieb:

    Danke für deinen Beitrag, wie würdest du dann mein "Problem" lösen?

    Ich sehe kein Problem.

    chmbw schrieb:

    So wie ich es vorher hatte mit:

    template <class Type>
    class Vector {
     
    public:
     
        Vector(Type x, Type y, Type z);
     
        template <typename T> friend T dotProduct(const Vector<T>& a, const Vector<T>& b);
     
    private:
        Type x;
        Type y;
        Type z;
     
    };
     
    template <class Type>
    Type dotProduct(const Vector<Type>& a, const Vector<Type>& b)
    {
      return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    

    Jo, ist doch prima. Allenfalls noch unbedeutende Kleinigkeiten,
    - den Konstruktorcode reinziehen, ist ja trivial,
    - sie Vector3 nennen,
    - die Kompunenten x,y,z darf eigentlich jeder lesen, der Lust drauf hat,
    - dann brauchts auch kein friend mehr.



  • asfdlol schrieb:

    mir ist suspekt wieso es keine getter für die vektor-komponenten gibt.

    Gibt es, ich wollte nur nicht den kompletten Code posten weil ich es als wenig hilfreich bzgl. meiner Frage erachtet habe.

    volkard schrieb:

    Jo, ist doch prima. Allenfalls noch unbedeutende Kleinigkeiten,
    - den Konstruktorcode reinziehen, ist ja trivial,
    - sie Vector3 nennen,
    - die Kompunenten x,y,z darf eigentlich jeder lesen, der Lust drauf hat,
    - dann brauchts auch kein friend mehr.

    Alles klar. Auch wenn es wohl mehr eine Glaubensfrage zu sein scheint, was spricht gegen private und friend für x,y,z?! Natürlich kann ich alles public machen, aber ich möchte halt nicht, dass die Einträge direkt manipuliert werden. Oder gibt es eine "read only" Möglichkeit (Mal von Getter/Setter abgesehen)?


Anmelden zum Antworten