Operator Überladung - Automatisch generieren lassen?



  • Hallo,

    ich wollte mal etwas bezüglich Operator-Überladung fragen. Und zwar angenommen ich habe eine kleine Mathe-Vektor Klasse so in dem Stil:

    template <class T>
    class Vector3D
    {
        /* Etwa 50 Zeilen Code */
    };
    

    Dann ist die Klasse ziemlich klein und schön übersichtlich, so wie ich mir das vorstelle.

    Jetzt hätte ich aber gerne auch sämtliche Operatoren für diese Vektor Klasse überladen, damit man damit einfacher rechnen kann. Also überlade ich etwa den + operator (global) wie folgt:

    template <typename T>
    Vector3D<T> operator+(Vector3D<T> const &lhs, Vector3D<T> const &rhs)
    {
        // Hier wird die Addition ausgeführt
    }
    

    Problem: Damit kann ich jetzt nur etwas wie my_vector1 = my_vector1 + my_vector2 . Schon sowas wie my_vector1 += my_vector2 geht nicht mehr weil ich dafür erst den += operator überladen muss, obwohl dieser ja eigentlich aus dem + Operator hergeleitet werden könnte.

    Also muss ich manuell +(binär), +(unär), +=, ++(prefix), ++(postfix) überladen. Gleiches mit minus oder den Vergleichsoperatoren. Meine anfangs schön kleine Header Datei ist jetzt schon vollgestopft mit etlichen Funktionen, von denen die meisten sehr redundant sind.

    Daher meine Frage: Warum generiert sich der Compiler diese Operatoren nicht selbst? Beispielsweise könnte man ja (in Sachen Addition) nur erlauben den + Operator zu überladen und der Compiler ersetzt dann etwas wie v1 += v2 direkt mit v1 = v1 + v2 . Wäre das nicht besser da weniger Arbeit, weniger Fehler und weniger "Verwirrung", etwa wenn jemand den += Operator anders überlädt als man es erwarten würde (also so dass v1 += v2 != v1 = v1 + v2 )?

    Und zweitens, gibt es vielleicht doch eine Möglichkeit sich die restlichen Operatoren aus den bisher bekannten automatisch generieren zu lassen?



  • Zu 2: Schau dir boost operators an.

    Zu 1: Ja, es wäre richtig cool wenn der Compiler Operatoren automatisch generieren lassen könnte. C++11 bietet sogar die Syntax dafür:

    bool operator==(const T &a, const T &b)
    {
      ...
    }
    
    bool operator!=(const T &a, const T &b) = default;
    // Compiler generiert:
    {
      return !(a == b);
    }
    

    Das wäre schon cool.



  • Das funktioniert mit CRTP und Friend name injection:

    template<typename Derived>
    struct AddAssignableSym
    {
    	friend Derived& operator+= (Derived& Lhs, Derived const& Rhs)
    	{
    		return Lhs = Lhs + Rhs;
    	}
    };
    

    Normalerweise ist es aber umgekehrt performanter (also die Zuweisungsversion selbst zu schreiben und die andere generieren zu lassen).



  • Nathan schrieb:

    bool operator!=(const T &a, const T &b) = default;
    // Compiler generiert:
    {
      return !(a == b);
    }
    

    Das wäre schon cool.

    Dann wohl eher:

    bool operator!=(const T &a, const T &b) = default;
    // Compiler generiert:
    {
      return !static_cast<bool>(a == b);
    }
    
    // oder meinetwegen:
    bool operator!=(const T &a, const T &b) = default;
    // <=>
    auto operator!= (T const& Lhs, T const& Rhs) -> decltype(!(Lhs == Rhs))
    {
        return !(Lhs == Rhs);
    }
    


  • Nathan schrieb:

    Zu 2: Schau dir boost operators an.

    Hm Ok, werd ich mir mal anschauen, danke. Wobei ich ja gerne auf boost verzichten würde (außerdem sieht das wieder sehr komplex aus für etwas eigentlich sehr simples...).

    Nathan schrieb:

    Zu 1: Ja, es wäre richtig cool wenn der Compiler Operatoren automatisch generieren lassen könnte. C++11 bietet sogar die Syntax dafür:

    Das wäre wirklich schön, ist sowas für die Zukunft geplant?

    asfdlol schrieb:

    Dann wohl eher:

    bool operator!=(const T &a, const T &b) = default;
    // Compiler generiert:
    {
      return !static_cast<bool>(a == b);
    }
    
    // oder meinetwegen:
    bool operator!=(const T &a, const T &b) = default;
    // <=>
    auto operator!= (T const& Lhs, T const& Rhs) -> decltype(!(Lhs == Rhs))
    {
        return !(Lhs == Rhs);
    }
    

    Wozu soll man hier noch den cast zu bool brauchen? (a == b) gibt doch schon ein bool zurück, wozu soll das dann nochmal gecastet werden?





  • manni66 schrieb:

    http://en.cppreference.com/w/cpp/utility/rel_ops/operator_cmp

    Ich hab einfach nur operator== als Beispiel genommen, weil ich keine Lust hab, mir die effizienteste Implementierung von operator+ mithilfe von operator+= zu überlegen und aufzuschreiben...
    Natürlich gibt es rel_ops, aber genauso wie boost.operators ist das nicht ganz dasgleiche wie ein language feature.

    @asdflol:
    Man braucht kein Cast oder decltype. Die = default Variante erwartet, dass eine Funktion mit derselben Signatur existiert, ansonsten ist das ein Fehler. Templates zählen auch nicht, das würde evtl. zu ungewollten Effekten führen.



  • manni66 schrieb:

    http://en.cppreference.com/w/cpp/utility/rel_ops/operator_cmp

    rel_ops ist ein komplettes Fehldesign, besser nicht verwenden.

    Boost hat den besseren Ansatz, man kann ihn auch leicht selber nachprogrammieren.



  • big_flops schrieb:

    rel_ops ist ein komplettes Fehldesign, besser nicht verwenden.

    Hm, warum denn? Was können für Probleme mit der Verwendung von rel_ops auftreten?



  • Da es sich um Templates handelt, werden die Operatoren für viel mehr Überladungen herangezogen, als du willst.



  • happystudent schrieb:

    Wozu soll man hier noch den cast zu bool brauchen? (a == b) gibt doch schon ein bool zurück, wozu soll das dann nochmal gecastet werden?

    Wenn man so etwas als Sprachfeature anbietet, dann soll es so allgemein wie möglich sein. Der ! -Operator ist überladbar und der == -Operator muss nicht notwendigerweise bool zurückgeben.



  • spoler schrieb:

    Da es sich um Templates handelt, werden die Operatoren für viel mehr Überladungen herangezogen, als du willst.

    Welche sind das?



  • asfdlol schrieb:

    Wenn man so etwas als Sprachfeature anbietet, dann soll es so allgemein wie möglich sein. Der ! -Operator ist überladbar und der == -Operator muss nicht notwendigerweise bool zurückgeben.

    Versteh ich nicht. Der ! -Operator ist überladbar, ja, aber doch nicht bezüglich des Rückgabewertes? Also wenn ich schreibe:

    bool operator==(MyClass const &lhs, MyClass const &rhs)
    {
        return lhs.value == rhs.value; // Irgendwas
    }
    
    bool operator!=(MyClass const &lhs, MyClass const &rhs)
    {
        return !(lhs == rhs);
    }
    

    dann ist die Signatur für MyClass ja schon "blockiert". Also ich kann dann eh keinen Overload für den !=Operator schreiben, der zwei MyClass Typen als Argument nimmt.

    Und wenn der ==operator tasächlich nicht ein bool zurückgibt (was ich aber schon ziemlich fragwürdig finden würde), warum sollte dann der daraus default-hergeleitete !=operator trotzdem (per erzwungenem cast) bool zurückgeben und nicht das selbe wie derjenige ==operator der für die default Herleitung hinzugezogen wurde?



  • happystudent schrieb:

    Versteh ich nicht. Der ! -Operator ist überladbar, ja, aber doch nicht bezüglich des Rückgabewertes?

    Doch?

    happystudent schrieb:

    Also wenn ich schreibe: [...] dann ist die Signatur für MyClass ja schon "blockiert". Also ich kann dann eh keinen Overload für den !=Operator schreiben, der zwei MyClass Typen als Argument nimmt.

    Richtig, und weiter?

    happystudent schrieb:

    Und wenn der ==operator tasächlich nicht ein bool zurückgibt (was ich aber schon ziemlich fragwürdig finden würde), warum sollte dann der daraus default-hergeleitete !=operator trotzdem (per erzwungenem cast) bool zurückgeben und nicht das selbe wie derjenige ==operator der für die default Herleitung hinzugezogen wurde?

    Weil der für den Rückgabewert vom == -Operator überladene ! -Operator möglicherweise semantisch etwas Anderes ausdrückt als der ! -Operator bei bool s.



  • happystudent schrieb:

    Und wenn der ==operator tasächlich nicht ein bool zurückgibt (was ich aber schon ziemlich fragwürdig finden würde), warum sollte dann der daraus default-hergeleitete !=operator trotzdem (per erzwungenem cast) bool zurückgeben und nicht das selbe wie derjenige ==operator der für die default Herleitung hinzugezogen wurde?

    Nehmen wir einfach mal das hier an:

    [code="cpp"]
    int operator==(MyClass const &lhs, MyClass const &rhs)
    {
        return lhs.value - rhs.value;
    }
    
    auto operator!=(MyClass const &lhs, MyClass const &rhs) = default;
    

    Was sollte der !=-Operator dann zurückgeben? Einen "Nicht"-Int? Oder -(lhs == rhs)? (Was ist in dem Fall mit Vorzeichenlosen Typen?)
    Oder soll tätlich ein boolscher Cast per ! vorgenommen werden?

    Das klingt halt alles sehr willkürlich...



  • Skym0sh0 schrieb:

    Nehmen wir einfach mal das hier an:

    int operator==(MyClass const &lhs, MyClass const &rhs)
    {
        return lhs.value - rhs.value;
    }
    
    auto operator!=(MyClass const &lhs, MyClass const &rhs) = default;
    

    Was sollte der !=-Operator dann zurückgeben? Einen "Nicht"-Int? Oder -(lhs == rhs)? (Was ist in dem Fall mit Vorzeichenlosen Typen?)
    Oder soll tätlich ein boolscher Cast per ! vorgenommen werden?

    Das klingt halt alles sehr willkürlich...

    Welchen Sinn sollte ein operator== haben, der einen int liefert?



  • Imho ein großer Fehler von C++ solch unsinnige Operatorenüberladungen zu erlauben. Erlaubt zwar Spielereien wie Boot.Spirit - aber ist sehr schlechter Stil.



  • asfdlol schrieb:

    happystudent schrieb:

    Versteh ich nicht. Der ! -Operator ist überladbar, ja, aber doch nicht bezüglich des Rückgabewertes?

    Doch?

    Ok, ich dachte du meintest mehrfach überladbar, Missverständnis.

    asfdlol schrieb:

    Weil der für den Rückgabewert vom == -Operator überladene ! -Operator möglicherweise semantisch etwas Anderes ausdrückt als der ! -Operator bei bool s.

    Ja schon, aber dann wärs doch besser eine Warnung zu generieren als das durch einen cast zu verschleiern oder? Halt in etwa wie wenn ich schreibe:

    bool b = 2; // warning C4305: 'initializing' : truncation from 'int' to 'bool'
    

    Skym0sh0 schrieb:

    Nehmen wir einfach mal das hier an:

    [code="cpp"]
    int operator==(MyClass const &lhs, MyClass const &rhs)
    {
        return lhs.value - rhs.value;
    }
    
    auto operator!=(MyClass const &lhs, MyClass const &rhs) = default;
    

    Was sollte der !=-Operator dann zurückgeben? Einen "Nicht"-Int? Oder -(lhs == rhs)? (Was ist in dem Fall mit Vorzeichenlosen Typen?)
    Oder soll tätlich ein boolscher Cast per ! vorgenommen werden?

    Das klingt halt alles sehr willkürlich...

    Hier würde ich eben wie oben beschrieben eine Warnung ausgeben. Wenn der Compiler einfach aus dem operator== einen operator!= mittels !(lhs == rhs) generiert dann ist doch alles eindeutig und nicht willkürlich.

    Wenn man wirklich so exotische Überladungen für diese Operatoren braucht (wie etwa einen int als Rückgabetyp) dann kann (bzw. muss) man sie halt immer noch explizit hinschreiben. Dann kann man sich aber auch nicht drüber beschweren, von daher seh ich das Problem nicht.

    In 99,9% der Fälle will man je eh einen bool als Rückgabetyp. Und in den paar Asnahmefällen schreibt man die entsprechenden Operatoren halt explizit hin.



  • Ethon schrieb:

    Imho ein großer Fehler von C++ solch unsinnige Operatorenüberladungen zu erlauben. Erlaubt zwar Spielereien wie Boot.Spirit - aber ist sehr schlechter Stil.

    rofl



  • Kellerautomat schrieb:

    Ethon schrieb:

    Imho ein großer Fehler von C++ solch unsinnige Operatorenüberladungen zu erlauben. Erlaubt zwar Spielereien wie Boot.Spirit - aber ist sehr schlechter Stil.

    rofl

    Ja weil

    std::string s2;
    std::getline(std::cin, s2);
    
    std::string s1 = "Hallo";
    s1.append(" ");
    s1.append(s2);
    
    std::cout.write(s1).endl();
    

    auch so geil und viel kürzer ist.

    Oder hättest gerne sowas wie in Java, wo keine Operatorenüberladung exitiert, außer bei dem im Sprachkern festgenagelten java.lang.String (was ja wohl eine blöde Ausnahme darstellt)??


Anmelden zum Antworten