Klasse zu Template-Klasse downcasten?

  • C++

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von Trade.

    Klasse zu Template-Klasse downcasten?

    Hi,

    ich hab nicht so viel Erfahrung mit Templates in C++, hab aber grade 'nen Einsatz dafür und bin mir nun an einer Stelle unsicher.
    Ich habe 'ne Klasse token, die einen mathematischen Token repräsentiert. Der Header sieht mal so aus (die Implementierung ist bisher noch nicht da):

    token.h

    C-Quellcode

    1. #ifdef _MSC_VER
    2. #pragma once
    3. #endif
    4. #define _USE_MATH_DEFINES
    5. #define pi M_PI
    6. #define e M_E
    7. #include <functional>
    8. #include "generic_token.h"
    9. #include <map>
    10. #include "math.h"
    11. #include <stack>
    12. #include <vector>
    13. using namespace std;
    14. namespace ExpParserPP {
    15. enum token_type { number, constant, function, _operator, bracket };
    16. class token
    17. {
    18. private:
    19. token_type type;
    20. static const map <wstring, std::function<void(stack<double>&)>> const_actions;
    21. static const map <wstring, std::function<void(stack<double>&)>> func_actions;
    22. static const map <wstring, std::function<void(stack<double>&)>> op_actions;
    23. public:
    24. token(token_type);
    25. static generic_token<double> read_num_token(const wstring&, uint32_t&);
    26. static generic_token<wstring> read_str_token(const wstring&, uint32_t&);
    27. void evaluate(stack<double>&);
    28. vector<token> calc_infix_tokens(const wstring&);
    29. };
    30. const map <wstring, std::function<void(stack<double>&)>> token::const_actions = {
    31. { L"e", [](stack<double>& s) {s.push(e);} },
    32. { L"pi", [](stack<double>& s) {s.push(pi);} }
    33. };
    34. const map <wstring, std::function<void(stack<double>&)>> token::func_actions = {
    35. { L"sin", [](stack<double>& s) {
    36. double val = s.top();
    37. s.pop();
    38. s.push(sin(val));
    39. }
    40. },
    41. { L"cos", [](stack<double>& s) {
    42. double val = s.top();
    43. s.pop();
    44. s.push(cos(val));
    45. }
    46. },
    47. { L"tan", [](stack<double>& s) {
    48. double val = s.top();
    49. s.pop();
    50. s.push(tan(val));
    51. }
    52. },
    53. { L"asin", [](stack<double>& s) {
    54. double val = s.top();
    55. s.pop();
    56. s.push(asin(val));
    57. }
    58. },
    59. { L"acos", [](stack<double>& s) {
    60. double val = s.top();
    61. s.pop();
    62. s.push(acos(val));
    63. }
    64. },
    65. { L"atan", [](stack<double>& s) {
    66. double val = s.top();
    67. s.pop();
    68. s.push(atan(val));
    69. }
    70. },
    71. { L"sqrt", [](stack<double>& s) {
    72. double val = s.top();
    73. s.pop();
    74. s.push(sqrt(val));
    75. }
    76. },
    77. { L"abs", [](stack<double>& s) {
    78. double val = s.top();
    79. s.pop();
    80. s.push(abs(val));
    81. }
    82. },
    83. { L"ln", [](stack<double>& s) {
    84. double exp = s.top();
    85. s.pop();
    86. s.push(log(exp));
    87. }
    88. },
    89. { L"lg", [](stack<double>& s) {
    90. double exp = s.top();
    91. s.pop();
    92. s.push(log10(exp));
    93. }
    94. },
    95. { L"log", [](stack<double>& s) {
    96. double exp = s.top();
    97. s.pop();
    98. double base = s.top();
    99. s.pop();
    100. s.push(log(exp) / log(base));
    101. }
    102. },
    103. };
    104. const map <wstring, std::function<void(stack<double>&)>> token::op_actions = {
    105. { L"+", [](stack<double>& s) {
    106. double f_sum = s.top();
    107. s.pop();
    108. double s_sum = s.top();
    109. s.pop();
    110. s.push(f_sum + s_sum);
    111. } },
    112. { L"-", [](stack<double>& s) {
    113. double sub = s.top();
    114. s.pop();
    115. double min = s.top();
    116. s.pop();
    117. s.push(min - sub);
    118. } },
    119. { L"*", [](stack<double>& s) {
    120. double f_fac = s.top();
    121. s.pop();
    122. double s_fac = s.top();
    123. s.pop();
    124. s.push(f_fac * s_fac);
    125. } },
    126. { L"/", [](stack<double>& s) {
    127. double divisor = s.top();
    128. s.pop();
    129. double dividend = s.top();
    130. s.pop();
    131. s.push(dividend / divisor);
    132. } },
    133. { L"!", [](stack<double>& s) {
    134. double neg = -s.top();
    135. s.pop();
    136. s.push(neg);
    137. } },
    138. { L"^", [](stack<double>& s) {
    139. double exp = s.top();
    140. s.pop();
    141. double base = s.top();
    142. s.pop();
    143. s.push(pow(base, exp));
    144. } }
    145. };
    146. }



    Ich werde vermutlich noch auf std::string umsteigen, bin nur gerade mal so verblieben, obwohl ich vermutlich keine 2 Byte-Character brauche.
    Meine Klasse generic_token sieht so aus (Header):

    generic_token.h

    C-Quellcode

    1. #ifdef _MSC_VER
    2. #pragma once
    3. #endif
    4. #include "token.h"
    5. using namespace std;
    6. namespace ExpParserPP {
    7. template <class T>
    8. class generic_token :
    9. public token
    10. {
    11. private:
    12. bool right_assoc;
    13. uint16_t prior;
    14. T val;
    15. public:
    16. generic_token(T, token_type);
    17. };
    18. template<class T>
    19. inline generic_token<T>::generic_token(T val, token_type type) :
    20. token(type)
    21. {
    22. this->val = val;
    23. if (type == number || type == constant)
    24. {
    25. prior = 100;
    26. }
    27. else if (type == _operator)
    28. {
    29. if (val == "+")
    30. {
    31. prior = 1;
    32. right_assoc = false;
    33. }
    34. else if (val == "-")
    35. {
    36. prior = 1;
    37. right_assoc = false;
    38. }
    39. else if (val == "*")
    40. {
    41. prior = 2;
    42. right_assoc = false;
    43. }
    44. else if (val == "/")
    45. {
    46. prior = 2;
    47. right_assoc = false;
    48. }
    49. else if (val == "%")
    50. {
    51. prior = 3;
    52. right_assoc = false;
    53. }
    54. else if (val == "!")
    55. {
    56. prior = 4;
    57. right_assoc = false;
    58. }
    59. else if (val == "^")
    60. {
    61. prior = 5;
    62. right_assoc = false;
    63. }
    64. }
    65. }
    66. }



    Der Konstruktor ist jetzt mal inline. Werde den natürlich noch in die .cpp werfen.
    So, ich habe da jetzt ein Template verwendet, weil die Tokens typspezifisch zur Verfügung stehen sollen. Also Zahlen als generic_token<double>, Operatoren als generic_token<std::string> usw. Trotzdem sollen die natürlich als Tokens behandelt werden und erben daher von token.
    Dieselbe Implementierung habe ich auch in C#: github.com/ProgTrade/SharpMath…Math/Expressions/Token.cs - Dann kann man sich vielleicht das Ganze besser vorstellen.

    Jedenfalls habe ich jetzt ein Problem bei der Implementierung:

    C-Quellcode

    1. void token::evaluate(stack<double>& stack)
    2. {
    3. switch (this->type)
    4. {
    5. case number:
    6. generic_token<double>* token = dynamic_cast<generic_token<double>*>(this);
    7. // Mit der map auswerten
    8. delete token;
    9. break;
    10. }
    11. }


    Äquivalent wäre hier das: github.com/ProgTrade/SharpMath…Expressions/Token.cs#L160
    Man sieht, ich stelle diese Methode in der Oberklasse zur Verfügung, frage den Typen ab und downcaste entsprechend zu generic_token<T> mit entsprechendem Typen. Das wollte ich nun in C++ implementieren. Da ich in token aber keine virutellen Methoden/Funktionen zur Verfügung stelle, meckert er: "The operand of a runtime dynamic_cast must have a polymorphic class type".

    Nun stellt sich mir die Frage: Ich kann natürlich einfach einen virtuellen Destruktor in token deklarieren, damit er nicht mehr meckert, obwohl ich den eig. gar nicht brauche, da ich keine Pointer o. ä. freigeben muss. Andererseits bin ich mir nicht sicher, ob die Implementierung daher dann architektonisch sauber ist. Schließlich könnte token die Methode ​evaluate zum Überschreiben anbieten und die Unterklasse macht das dann. Das Problem hier ist allerdings, dass diese ja eine Template-Klasse ist und ich ja nicht einfach die Methode für generic_token<double> usw. überschreiben kann. Daher habe ich das auch in C# so mit dem Downcasten gelöst. Oder geht das irgendwie sauber in C++? Wie gesagt, man hat da ja in C++ 'nen Haufen Möglichkeiten, aber von dem Template-Zeugs hab' ich nicht viel Plan.

    Wie sollte ich das also am Besten lösen? Passt das so mit dem Downcasten über dynamic_cast und einem virtuellem Member (evtl. Destruktor)? Oder gibt es bessere Wege?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    @Trade Was sagt dieses schnöde Casten:

    C-Quellcode

    1. generic_token<double>* token = (generic_token<double>*)(this);
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Soweit ich weiß, sollte man C-Style Casting in C++ doch nicht verwenden und auf static_cast<>, dynamic_cast etc. zurückgreifen, oder? ;)

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    1. Denke ich, dasss evaluate tatsächlich überschreibbar sein sollte.
    2. Könnt ich mir hier gut Vorstellen, dass template specialization dafür ganz geschickt verwendet werden kann, indem du evaluate eben spezialisierst

    Edit: das ist wohl richtig, dass C casts in C++ als unschön gelten
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    @Trade Mir hat das noch keiner verboten. :D
    Probier es doch einfach mal aus. Und wenn Du kannst, mit mehreren Compilern.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @jvbsl Danke. Von Template specialization hatte ich bisher noch nichts gehört. Hab's mir mal kurz auf der CppReference angeschaut und das scheint sowas zu sein, wie ich gesucht hatte. :) Das finde ich extrem nützlich, dass C++ sowas hat.
    @RodFromGermany Ich werde mich mal der Template specialization annehmen, da das die bessere Lösung scheint, als das Zeugs einfach zu casten, wenn man das architektonisch hier anders lösen kann. Und C-Casts wollte ich eigentlich auch vermeiden. :D

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Mach den Destruktor von token mal virtuell, dann klappts. Ich würde dass mit dem token_type aber lassen bzw. einschränken und unterteilen zwischen token<int/long/double>, token<const int/const long/const double> und token<string>. Dann sparst du dir die ganzen Switche.

    Sieht mir wie'n Rechner aus. Vielleicht kann man Operation anstatt eines Strings direkt n Lamda hinterlegen. Müsste man mal abwägen wie komfortabel dass Ganze wird.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Gonger96“ ()

    Gonger96 schrieb:

    Mach den Destruktor von token mal virtuell, dann klappts.
    Jo, aber ich denke Template Specializations erlauben es mir, das sauberer zu lösen, als mit irgendwelchen Casts rumzuhantieren, oder? Den Destruktor brauche ich ja eigentlich nicht, d.h. er würde leer bleiben.

    Gonger96 schrieb:

    Ich würde dass mit dem token_type aber lassen bzw. einschränken
    Das Problem ist, dass bspw. ein Operator genauso wie eine Funktion als generic_token<std::string> repräsentiert werden kann. Folglich muss ich über einen weiteren Anhaltspunkt herausfinden können, um was es sich handelt, um die geeignete std::function aus der entsprechenden Map darauf anwenden zu können. Dazu eben die Enum. Oder habe ich Dich falsch verstanden?

    Und ja, das wird ein Rechner/Expression Parser. ;)

    Gonger96 schrieb:

    Vielleicht kann man Operation anstatt eines Strings direkt n Lamda hinterlegen.
    Inwiefern? Was meinst Du mit "Operation"? Bezüglich der entsprechenden Maps? Und wie würde das dann aussehen?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Sofern da mal iwas wichtiges gemacht werden soll z.B. Speicher freigeben, dann musser virtuell sein, sonst halt nicht. Die map<.., function<>> hatte ich erst garnicht gesehen, dass ist echt nicht schlecht so.

    Wenn dus jetzt über spez. Templates löst, wirst du alles was ein Token nimmt als Template machen müssen. So wirds in der Stl auch gemacht, ist mehr Programmieraufwand, verbraucht mehr Speicher ist allerdings schneller und flexibler als dynamische Polymorphie durch Vererbung. Falls Ersteres, dann streich die generic_token und mach von token ein token<>. Vielleicht würde es sich anbieten jede Tokenart (Operation, Function, Braket etc) als struct zu implementieren und denen eine evaluate() Funktion zu geben. Das wäre imho sauberer als den Typen über eine Enum anzugeben.
    Stimmt, so habe ich noch gar nicht gedacht. Dann würde ich mir die ganzen Templates und Switches sparen und einfach die verschiedenen Tokens eindeutig zuordnen, die dann ausgewertet werden können.

    Die einzige Baustelle wäre dann token::read_str_token.
    Da ja im Voraus nicht bekannt ist, was das zurückgibt, müsste der Rückgabetyp halt generell erstmal token sein. Aber das ist ja egal, da evaluate ja vererbt wird und ich das einfach aufrufen kann, egal, was sich genau für eine Token- Unterklasse dahinter befindet. Das setzt natürlich voraus, dass die von token erben, was auch zu einer struct würde.

    C-Quellcode

    1. virtual void evaluate(stack<double>&) = 0;


    Dann fiele die Enum weg. Meinst Du das so?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Jo, dachte da an sowas:
    Spoiler anzeigen

    C-Quellcode

    1. template <typename t>
    2. void evaluate(t)
    3. {
    4. throw invalid_argument(string("Unable to evaluate ") + typeid(t).name());
    5. }
    6. template <>
    7. void evaluate(double d) // Numeric & Constant
    8. {
    9. cout << "Evaluate double\n";
    10. };
    11. using func = function<double(double)>;
    12. typedef double(*func2)(double);
    13. #define MAKE_FUN(x) func(static_cast<func2>(x))
    14. template <>
    15. void evaluate(func f) // Function
    16. {
    17. cout << "Evaluate func\n";
    18. }
    19. enum class operator_type {add, sub, mul, div, inv, pow};
    20. template <>
    21. void evaluate(operator_type op) // Operator
    22. {
    23. cout << "Evaluate operator\n";
    24. }
    25. int main(int argc, wchar_t* argv[])
    26. {
    27. try
    28. {
    29. // Inputstring auswerten, stack<> einfach als Parameter in evaluate<>() durchreichen
    30. // Zahl
    31. evaluate(5.7);
    32. // Funktion
    33. evaluate(MAKE_FUN(sin));
    34. // Operator
    35. evaluate(operator_type::add);
    36. // ...
    37. // Invalid
    38. evaluate("Huhu");
    39. cout.flush();
    40. }
    41. catch (exception& e)
    42. {
    43. cerr << e.what() << endl;
    44. }
    45. return EXIT_SUCCESS;
    46. }
    Jetzt hast Du mich etwas verwirrt. :D
    Inwiefern steht das jetzt in Verbindung mit den verschiedenen Tokens und der Polymorphie? Das wäre ja nämlich eine generische Methode evaluate, die so für mehrere Typen implementiert wird. Allerdings bräuchte ich ja gar keine Templates mehr, wenn ich zwischen den einzelnen Token unterscheide, oder? Denn jeder Token hätte die dann, würde den Stack bekommen, seine Aufgabe erledigen und das Ergebnis auf den Stack pushen.

    Und dann verstehe ich ein paar Sachen in Deinem Code nicht:

    Gonger96 schrieb:

    Inputstring auswerten, stack<> einfach als Parameter in evaluate<>() durchreichen
    Wie genau meinst Du das dort? Die Parameter sind bei Dir ja jeweils die unterschiedlichen Values der Token, aber dafür bräuchte ich ja den Parameter stack<double>&.

    Dann habe ich das Konstrukt so noch nie gesehen:

    C-Quellcode

    1. typedef double(*func2)(double);
    2. #define MAKE_FUN(x) func(static_cast<func2>(x))


    Was genau macht das? Typedefs an sich sind mir natürlich bekannt, aber das, was da definiert wird, verstehe ich nicht ganz. ^^ Also ich weiß nicht ganz, was func2 dann ist/repräsentiert. Die zweite Zeile verstehe ich, nur eben nicht den Zieltyp des Casts.
    Und wie genau würde ich wissen, was ich evaluate mitgeben muss? Es sind ja unterschiedliche Typen, die Du da angibst.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Richtig, dass ist nur diese eine Methode. Ohne Polymorphie. Müsste so auch ziemlich gut hinhauen, wenn ich dich nicht falsch verstanden habe. Du müsstest nur jeweils den stack<> noch als Parameter hinzufügen, war ich zu faul zu :D func2 ist die Definition eines Funktionszeigers für double __cdecl function(double); Weil std::sin() überladen ist muss man den durchn static_cast jagen, damit der Compiler weiss welche Überladung gemeint ist. MAKE_FUN macht dir aus ner überladenen Funktion eine function<double(double)>.
    Ah okay, Faulheit. :P
    Und das ist ein Funktionszeiger, ah, das klingt schon mal logischer. Danke dafür.

    Achso, daher meintest Du das mit token<>, oder? Also die Algorithmen über den Term laufen lassen und dann die verschiedenen Tokens, die gelesen wurden, in einer Liste halten, die dann durchlaufen und jeweils evaluieren. Dazu müsste ich ja evaluate einfach die jeweilige Value übergeben bzw. diese halt ermitteln.
    Allerdings kann ich ja bspw. keinen vector<token> machen, wenn dort ein Template dranhängt, weil er ja einen Typ möchte. Würde das nicht wieder auf Vererbung rauslaufen?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Also du bekommst als Input vermutlich nen String. Wie du den interpretierst weiß ich nicht, RegEx vielleicht? Is auch inner Std Lib. Ich würde diese Teilstrings dann erstmal in n vector<string> legen geordnet nach Operationsreihenfolge. Danach alle durchloopen und dabei interpretieren was es ist, den String casten und dann evaluate aufrufen. Falls man so zweimal prüfen muss obs ne Zahl, Konstante, Funktion etc ist, könntest du direkt alles in ein vector<pair<string, enum>> packen.

    Grüße
    Verstehe. Das klingt mir aber etwas umständlich, ich würde das schon lieber etwas polymorph trennen. ^^

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Du wertest sämtliche Token in einer Methode aus zusätzlich zu den ganzen Maps<>. Imho wartbarer wenns aufgeteilt ist. Man muss ja nicht unbedingt ein Template nehmen um es nachher wieder in seine Typen aufzudröseln. Du kanst es natürlich machen wie du willst ;) Zur Basisklasse casten geht halt nur bei polymorphen Klassen, was ja auch Sinn macht.
    Ja, im Moment. ^^ Aber ich möchte es ja auf verschiedene Klassen aufteilen, die das eigens implementieren.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!: