wie würde so etwas in C++ aussehen?



  • Hallo,

    ich habe gerade ein kleines Beispielprogramm in Scala geschrieben. Ich benutzte Pattern matching mit Decomposition.

    http://pastebin.com/U6acbMKF

    Ich denke es sollte eigentlich recht klar sein was in dem Programm passiert. Man kann Expressions definieren und diese verbinden. Man hat eine toString Method die die Expression in ein lesbares Format umwandelt und eine eval Method die das Ergebnis berechnet.

    (Comments = Output)

    Wäre so etwas in C++ viel mehr Arbeit? Kann man solche Probleme auch elegant in C++ lösen?



  • kantaki schrieb:

    Wäre so etwas in C++ viel mehr Arbeit? Kann man solche Probleme auch elegant in C++ lösen?

    Du wolltest es so ...

    #define MAKE_OPERATOR(name, op)                                         \
      template <typename Lhs, typename Rhs>                                 \
      struct name##_ {                                                      \
        Lhs lhs; Rhs rhs;                                                   \
        friend std::ostream& operator<<(std::ostream& os, name##_ const& n) \
        { return os << '(' << n.lhs << #op << n.rhs << ')'; }               \
        friend auto eval(name##_ const& n) -> decltype(eval(n.lhs) op eval(n.rhs)) \
        { return eval(n.lhs) op eval(n.rhs); }                              \
      };                                                                    \
      template <typename Lhs, typename Rhs, typename Res=name##_<Lhs,Rhs> > \
      Res name(Lhs const& lhs, Rhs const& rhs) { return Res{lhs, rhs}; }
    
    double eval(double d) { return d; }
    MAKE_OPERATOR(prod, *)
    MAKE_OPERATOR(sum,  +)
    MAKE_OPERATOR(div,  /)
    
    int main()
    {
      auto a = prod(sum(sum(2, 3), 1), 5);
      std::cout << a << " = " << eval(a) << '\n';
    
      auto b = div(sum(28, 2), prod(5, 2));
      std::cout << b << " = " << eval(b) << '\n';
    }
    

    http://ideone.com/uJvE2

    Und jetzt noch der Vergleich zur Scala-Variante:

    • Ein neuer Operator wird genau an einer Stelle definiert, bei dir sind es 3 Stellen.
    • Dein "Pattern matching mit Decomposition" nennt man in C++ switch-case und ist da zu Recht schlechter Stil. Ich liebe Pattern Matching, wenn es richtig benutzt wird, aber du wendest es in deinem Beispiel am falschen Ort an.
    • Mein Design lässt sich einfach erweitern. Z.B. wird für eine Unterstützung von std::complex nur eine Zeile benötigt. Und bei dir?


  • Sieht ziemlich elegant aus muss ich sagen. Merkwürdig, viele Behaupten C++ sei sehr verbose doch das sieht recht schön und kompakt aus. Obwohl ich doch den Scala code schöner finde, ist vielleicht aber auch Geschmackssache.

    Wie sieht es denn aus wenn man komplexere Arithmetik benutzen möchte zb factorial? Wäre es immernoch einfach erweiterbar?

    MAKE_OPERATOR(fac,  some_factorial )
    

    Ja ich müsste in meinem Beispielprogramm 3 Zeilen hinzufügen class, case für show und case eval.

    Es geht sicherlich auch noch schöner, bin noch ein Scala neuling.



  • Factorial geht auch. Ist etwas komplizierter, weil es kein binärer sonder ein unärer Operator ist.

    Sehr verbose formatiert sähe das so aus (vielleicht kann man es so etwas besser lesen):

    long long fac(int n)
    {
      return n <= 0 ? 1 : n*fac(n-1);
    }
    
    template <typename T>
    struct factorial_ {
      T val;
    
      friend std::ostream& operator<<(std::ostream& os, factorial_ const& f)
      {
        return os << '(' << f.val << ")!";
      }
    
      friend long long eval(factorial_ const& f)
      {
        return fac(static_cast<int>(eval(f.val)));
      }
    };
    
    template <typename T>
    factorial_<T> factorial(T const& val)
    {
      return factorial_<T>{val};
    }
    
    // dann im main:
    auto c = prod(1, sum(factorial(sum(2, 3)), 0));
    std::cout << c << " = " << eval(c) << '\n';
    // Ausgabe: (1*(((2+3))!+0)) = 120
    

    Wichtig ist zu sehen, dass der übrige Teil des Programmes nicht verändert werden musste. Du müsstest an 3 Stellen etwas abändern und wenn du eine vergisst crasht das Programm. Wenn ich z.B. die eval-Funktion vergessen hätte gäbe es einen Compilerfehler.

    Aber stimmt schon, es könnte auch in C++ etwas mehr dense sein. In dem im letzten Post definierten Makro sind 2 Workarounds drin für etwas, was eigentlich die Sprache selber können sollte.



  • Ich habe gefragt weil ich mich jetzt auf eine Sprache spezialisieren möchte und ich war mir nicht sicher ob es C++ oder Scala werden sollte.

    Ich muss sagen der C++ code sieht recht hübsch aus.

    Ich bekomme auch einen Compiler error, es wird sogar in der IDE rot makiert, da nicht alle cases genutzt werden würden.

    C++ ist defenitiv mehr verbreitet, jetzt muss ich mich nur noch entscheiden. Aber als Anfänger der nicht alle Features kennt ist das natürlich nicht gerade einfach.



  • kantaki schrieb:

    Aber als Anfänger der nicht alle Features kennt ist das natürlich nicht gerade einfach.

    Kleine Entscheidungshilfe: Wenn man C++ aus dem FF kennt und weiß, wie es "unter der Haube" funktioniert, dann ist der Einstieg in eine große Menge anderer Sprachen ein Klacks.



  • Ich lese jetzt öfters dass es hilft, zu wissen wie C++ unter der Haube so funktioniert. Gibt es da ein gutes Buch drüber, oder auch Webseiten die mal einen kleinen Einblick geben können?



  • Butterbrot schrieb:

    Gibt es da ein gutes Buch drüber

    Ja, gibt es.
    Inside the C++ Object Model | ISBN: 0201834545



  • kantaki schrieb:

    Ich denke es sollte eigentlich recht klar sein was in dem Programm passiert.

    Nein, fuer mich nicht.



  • hier die scala version zum c++ programm

    http://pastebin.com/U39Z5UKG


Anmelden zum Antworten