Operatorn überladen - Wann global, wann als Element?



  • Hey bestimmt schon tausend mal gefragt, aber hab auf Anhieb keine passende Lösung gefunden.
    Wie muss ich einen Operator (Beispiel Addition) überladen, damit das folgende Beispiel geht?

    class Bruch {
    int zaehler;
    int nenner;
    
    //Konstruktor etc.
    Bruch operator+(Bruch pBruch);
    Bruch operator+(int pZahl);
    Bruch operator+(int pZahl1, int pZahl2);
    } 
    
    int main() {
    Bruch bruch1(10,2);
    Bruch bruch2(3,4);
    Bruch bruch3(0,0);
    //Alle möglichen Fälle von Addition die mir gerade einfallen
    bruch3 = bruch1 + bruch2;
    bruch2 = bruch1 + 5;
    bruch1 = 5 + bruch2;
    bruch3 = 10 + 10; //edit: Okay das wäre kein Fall der Addition, sondern nur eine Zuweisung...
    }
    

    Danke!

    P.S: Jede Operation gibt ja auch immer etwas zurück (= z.B. Das was rechts von = steht), muss ich da irgendwas beachten? Dass ich sowas vielleicht "const" mache oder so?



  • Was geht den nicht?

    OK. Also du brauchst sicher mal einen anstänigen Konstruktor für Bruch. Dann wären Implementierungen der Funktionen auch sicher noch von Vorteil.
    Sprich: Mach mal, und wenn du konkrete Probleme hast, dann frag.

    EDIT:
    Du kannst die erzeugen Objekte const machen, um die Zuweisung an temporäre Objekte zu verbieten..



  • ja meine Frage ging in diese Richtung:

    //...
    Bruch operator+(int pZahl) // für Bruch = Bruch + 1;
    Bruch operator+(int pZahl, Bruch pBruch) // für BruchA = 1 + BruchB
    Bruch operator+(Bruch, pBruch, int pZahl)  // für BruchA = BruchB + 1
    

    Den 1.Fall kann ich als Elementfunktion implementieren
    Fall 2 und 3 muss ich doch als globale Funktion schreiben oder? Bzw. muss ich zwei Versionen schreiben, da die an sich (abgesehen von der Reihenfolge) ja gleich sind.
    Wenn ich die Funktionen global Schreibe, dann muss ich bei der Implementierung doch immer ein neues Objekt erschaffen (welchs dann auch const sein kann), also so:

    const Bruch operator+(int pZahl, Bruch pBruch) {
        return Bruch(pBruch.nenner + pZahl, pBruch.zaehler + pZahl)
    }
    

    Meine Fragen wäre nun konkret:
    - Muss ich zwischen Fall 2 und 3 unterscheiden oder reicht dort eine Funktion?
    - Soll der 1. Fall als Elementfunktion implementiert werden oder auch global? (Also so "Bruch operator+(Bruch pBruch, Bruch pBruch)"), sodass dann die Klasse an sich nur noch die Konstruktoren und Zuweisungsoperatoren enthält.



  • Schreib eine Funktion, die 2 Brüche übernimmt global und dann mach für Brüche Konstuktoren, die dir die Umwandlung von int übernimmt. Dann hast du lediglich eine Funktion.

    Bruch const operator + (const Bruch & left , const Bruch & right );
    


  • Joa, das ist eine recht elegante Idee, danke!
    Aber wie muss ich denn dann die Umwandlung implementieren?

    Bruch = Bruch + 5;
    
    //als Elementfunktion
    Bruch operator=(Bruch pBruch) {
    return Bruch(this.nenner = pBruch.nenner, this.zaehler = pBruch.zaehler)
    }
    
    Bruch(int pZahl) {
    nenner = pZahl; zaehler = pZahl;
    }
    
    //globale Funktion
    Bruch const operator + (const Bruch & left , const Bruch & right ) {
    return Bruch(left.nenner + right.nenner, left.zaehler + right.zaehler)
    }
    

    Woher weiss denn dann der Compiler, dass er erst den Konstruktor für Brüche und dann die Additionsfunktion aufrufen soll?



  • Weil der Konstruktor nicht explicit ist, das heisst, dass er, wenn er einen Datentypen findet, der zu einem Konstruktor passt er diesen Konstruktor impliziet aufrufen darf.

    Bruch(int pZahl) {
    nenner = pZahl; zaehler = pZahl;
    }
    

    Sollte wohl eher:

    Bruch(int pZahl) {
    nenner = 1; zaehler = pZahl;
    }
    

    sein..

    Operator = erwartet ja 2 Brüche. Und da er direkt keine findet, probiert er durch den Aufruf des Konstruktors welche zu konstruieren. Was ja geht, da du int als Argument akzeptierst. (spiel ruhig auch mal mit dem explicit Schlüsselwort rum, dann siehst du, was da passiert).



  • Hab gerade mal eine ältere Vektorklasse umgeschrieben und um templates erweitert und wollte dort Operatoren überladen:

    #include "Vektor.h"
    
    int main() {
        Vektor<int> Test(1,2,3);
        std :: cout << Test.GetX() << std :: endl;
        std :: cout << Test.GetY() << std :: endl;
        std :: cout << Test.GetZ() << std :: endl;
    
        Test = Test + 2;
    
        std :: cout << Test.GetX() << std :: endl;
        std :: cout << Test.GetY() << std :: endl;
        std :: cout << Test.GetZ() << std :: endl;
    }
    
    /* Vektor.h */
    class Vektor {
    
        private:
            T x; T y; T z;
    
        public:
            Vektor(T pX, T pY, T pZ);
            Vektor(T pAll);
            Vektor(const Vektor &pVektor);
    
            Vektor& operator=(const Vektor &pVektor);
            Vektor& operator+=(const Vektor &pVektor);
            Vektor& operator-=(const Vektor &pVektor);
            Vektor& operator*=(const Vektor &pVektor);
            Vektor& operator/=(const Vektor &pVektor);
            const Vektor& EinheitsVektor() const;
    
            T GetLen() const;
            T GetX() const;
            T GetY() const;
            T GetZ() const;
    };
    
    template <typename T>
    const Vektor<T> operator+(const Vektor<T> &pVektor1, const Vektor<T> &pVektor2) {
        return Vektor<T>(pVektor1.GetX() + pVektor2.GetX(), pVektor1.GetY() + pVektor2.GetZ(),pVektor1.GetZ() + pVektor1.GetZ());
    }
    

    Error: no match for 'operator+' in 'Test + 2'|



  • Ja, das ist, weil eine Klassentemplate den Typen wissen muss. Also musst du das den Typen explizit angeben.

    Test = Test + Vektor<int>( 2 );
    


  • Aber dies hier geht doch:

    template <typename T>
    const Vektor<T> operator+(T zahl, const Vektor<T> &pVektor) {
    //Variante 1: Performancemäßig besser würde ich sagen
        return Vektor<T>(zahl + pVektor.GetX(), zahl + pVektor.GetZ(),zahl + pVektor.GetZ());
    //Variante 2: Sieht schöner aus
    return Vektor<T>(zahl) + pVektor;
    }
    
    /* Aufruf */
    Test = 2 + Test;
    

    Mal ganz ab davon welche Version nun schöner aussieht/besser ist, müsste ich doch für die Addition(usw) 3 Funktionen schreiben wenn ich das wie oben angegeben Aufrufen möchte oder nicht?



  • /Edit: sry - Thread zu ungenau gelesen



  • Ein kleiner Verweis auf Boost.operator: die als Methoden angelegten Operatoren werden hiermit auch global.



  • Pille456 schrieb:

    Mal ganz ab davon welche Version nun schöner aussieht/besser ist, müsste ich doch für die Addition(usw) 3 Funktionen schreiben wenn ich das wie oben angegeben Aufrufen möchte oder nicht?

    Nein, du hast ja einen nicht expliziten Konstruktor mit einem Parameter vom Typ T . Da sollte es reichen, einen Operator für zwei Vektoren zu definieren, dann wird T implizit in Vektor<T> umgewandelt.

    Dir ist aber schon bewusst, dass die Addition von Vektoren und skalaren Grössen in der Mathematik nicht sinnvoll ist? Auch der Konstruktor mit einem Parameter könnte sehr verwirrend sein. Kommt es denn bei dir so häufig vor, dass ein Vektor mit drei gleichen Komponenten verwendet wird? Ich glaube nicht, und selbst wenn, würde ich keine solche Funktionalität anbieten (zumindest nicht in der Klasse).



  • Nexus schrieb:

    Dir ist aber schon bewusst, dass die Addition von Vektoren und skalaren Grössen in der Mathematik nicht sinnvoll ist?

    Wie kommst Du auf die Annahme?



  • Nexus schrieb:

    Nein, du hast ja einen nicht expliziten Konstruktor mit einem Parameter vom Typ T . Da sollte es reichen, einen Operator für zwei Vektoren zu definieren, dann wird T implizit in Vektor<T> umgewandelt.

    Nun wie schon gesagt, ist dies nicht der Fall:

    class Vektor {
            //...
            template <typename T2>
            Vektor(T2 pAll);
    
    };
    
    template <typename T1, typename T2>
    const Vektor<T1> operator+(const Vektor<T1> &pVektor1, const Vektor<T2> &pVektor2) {
        return Vektor<T1>(pVektor1.GetX() + pVektor2.GetX(), pVektor1.GetY() + pVektor2.GetY(),pVektor1.GetZ() + pVektor2.GetZ());
    }
    
    int main() {
        Vektor<float> Test(1.2,2,3);
    
        std :: cout << Test.GetX() << std :: endl;
        std :: cout << Test.GetY() << std :: endl;
        std :: cout << Test.GetZ() << std :: endl;
    
        Test += 2;
    
        std :: cout << Test.GetX() << std :: endl;
        std :: cout << Test.GetY() << std :: endl;
        std :: cout << Test.GetZ() << std :: endl;
    }
    

    error: no match for 'operator+' in 'Test + 2'



  • im Josuttis/Vandervoorde steht in nem Nebensatz was von

    C++ Templates - The Complete Guide, S.16 schrieb:

    Because automatic type conversion is not considered for templates...

    Das erklärt vermutlich warum er den op+ nicht findet...



  • Also für den Fall dass ich templates verwende muss ich doch 3 Funktionen schreiben? (Also für den Fall "Vektor + Vektor", "Zahl + Vektor" und "Vektor + Zahl")



  • müsstest du, ja.
    Ich würde mir allerdings eher Gedanken darüber machen, welchen Sinn ein Konstruktor mit nur einem Argument für einen Vektor hat, genauso darüber, welchen Sinn eine Addition eines Vektors mit einem Skalar haben könnte. Mathematisch betrachtet ist nämlich beides ziemlicher Humbug.

    /edit: Standard-Auszug zum argument-deduction-Problem:

    14.8.2.1 Deducing template arguments from a function call [temp.deduct.call] schrieb:

    1 Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below.

    2 If P is not a reference type:

    If A is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is used in place of A for type deduction; otherwise,
    If A is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3) is used in place of A for type deduction; otherwise,
    If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction.
    If P is a cv-qualified type, the top level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction.
    3 In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:

    If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than A.
    A can be another pointer or pointer to member type that can be converted to the deduced A via a qualifi cation conversion (4.4).
    If P is a class, and P has the form template-id, then A can be a derived class of the deduced A. Like wise, if P is a pointer to a class of the form template-id, A can be a pointer to a derived class pointed to by the deduced A.
    These alternatives are considered only if type deduction would otherwise fail. If they yield more than one possible deduced A, the type deduction fails. [Note: if a template-parameter is not used in any of the function parameters of a function template, or is used only in a non-deduced context, its corresponding template-argument cannot be deduced from a function call and the template-argument must be explicitly specified. ]

    Implizite Konvertierungen jenseits der aufgeführten werden eben nicht in Betracht gezogen.



  • Tachyon schrieb:

    Wie kommst Du auf die Annahme?

    Ein Vektorraum definiert unter seinen Elementen nur die Operationen Addition und Multiplikation mit einem Skalar. Klar sind Verknüfpungen wie beispielsweise Skalar- und Vektorprodukt möglich, diese haben aber auch eine Verwendung in der Mathematik.

    Wie kommst du denn auf die Gegenannahme?

    pumuckl schrieb:

    Ich würde mir allerdings eher Gedanken darüber machen, welchen Sinn ein Konstruktor mit nur einem Argument für einen Vektor hat, genauso darüber, welchen Sinn eine Addition eines Vektors mit einem Skalar haben könnte. Mathematisch betrachtet ist nämlich beides ziemlicher Humbug.

    👍



  • Nexus schrieb:

    Dir ist aber schon bewusst, dass die Addition von Vektoren und skalaren Grössen in der Mathematik nicht sinnvoll ist?

    Nexus schrieb:

    Ein Vektorraum definiert unter seinen Elementen nur die Operationen Addition und Multiplikation mit einem Skalar.[...]

    😕



  • Tachyon schrieb:

    😕

    Drück dich bitte mal anständig aus. Man kann so ja nur erraten, worauf du hinauswillst.

    Auch deine Fragen ("Wie kommst du auf diese Annahme?") dürften ruhig etwas aussagekräftiger formuliert sein. Beispielsweise könntest du erwähnen, was für dich nicht ins Konzept passt.

    Edit: Ich habe möglicherweise deine Anspielung erraten:
    Ich habe mich ein bisschen unklar ausgedrückt.

    • Mit "Addition von Vektoren und skalaren Grössen" im ersten Teil meinte ich Addition, bei der ein Operand ein Vektor und einer ein Skalar ist.
    • "Addition und Multiplikation mit einem Skalar" (zweiter Teil) bedeutet Addition mit zwei Vektoren als Operanden beziehungsweise Multiplikation eines Vektors mit einem Skalar.

    Wenn es das war, sorry für das Missverständnis, es war wirklich zweideutig formuliert. Oben genannte Kritik an deinen Fragestellungen gilt natürlich weiterhin. 😉


Anmelden zum Antworten