ich benutze CubeIDE um STM32 Prozessoren zu bespaßen. Es gelang mir mit #include <complex.h> und "float complex z;" die Rechnung mit komplexen Zahlen in C++ komfortabel zu machen, vorher hatte ich das hingefummelt.
Das gleiche möchte ich jetzt mit 3D-Vektoren machen. Ich möchte also dass der Compiler "float vector v;" versteht.
C++ hat keine integrierten Klassen für Vektoren fixer Länge (valarray ist für dynamische Länge). Du musst es dir selbst implementieren oder eine der Millionen Bibliotheken für lineare Algebra nutzen.
Im K&R-C-Buch steht drin, wie man Header-Dateien anlegt oder Wikipedia
ist auch ganz gut:
Da steht aber nur C drin. In C++ gibt es exzellente Möglichkeiten, solche Dinge zu kapseln und komfortabel nutzbar zu machen. Das ist zwar auch eine nette Übung, erfindet aber das Rad zum tausendsten Mal neu, denn Algebra-Bibliotheken gibt es sehr viele, z.B. Eigen. Fortran ist hier allerdings total unhilfreich.
Es werden ja vermutlich auf dem Controller keine allzu komplexen Algorithmen benötigt. Wichtiger ist daher ein übersichtliches und gut nutzbares API.
ich benutze CubeIDE um STM32 Prozessoren zu bespaßen. Es gelang mir mit
#include <complex.h> und "float complex z;" die Rechnung mit komplexen
Zahlen in C++ komfortabel zu machen, vorher hatte ich das hingefummelt.
Das gleiche möchte ich jetzt mit 3D-Vektoren machen. Ich möchte also
dass der Compiler "float vector v;" versteht.
Welches include brauche ich denn dafür?
So etwas gibt es nicht. Es gäbe in C++ die Möglichkeit, ein std::array<float, 3> zu erzeugen, aber da gibt es keine mathematischen Operationen. Ein std::valarray<float> könnte man auch verwenden, aber das hat eine dynamische Größe, die an der Stelle zu unnötigem Overhead führt. Mathematische Operationen gibt es dafür, aber nur elementweise.
Am besten erzeugst du entweder selbst eine Klasse für deinen 3D-Vektor, oder du suchst dir eine, die schon fertig ist und kann, was du brauchst.
Hi,
das Entscheidende für mich sind die Operatoren, wenn man immer add(a,b) schreiben muss wirds unübersichtlich.
Aber ich habs hingekriegt, vielleicht nicht schön, aber geht und ist nach 50Jahren C vllt. die zarte Pflanze der neuen Liebe zu C++.
Da ist bestimmt noch ne Macke drin, ich habs nur etwas getestet, also Vorsicht.
Und noch Fragen an die Kundigen:
Die Komponenten des Vektors sind jetzt float. Wie kann man das variabel machen sodass man float vec_t a und double vec_t b schreiben kann? Die skalaren Rückgabewerte sollten dann auch entsprechend sein.
Für const mal vector und vector mal const definiere ich ja jeweils einen eigenen Operator. Geht das auch eleganter?
Nur standen viele gute
Programierhilfen zum Thema Mathe und Computer eben in Fortranbüchern.
Wie gesagt sind die Algorithmen ziemlich simpel. Ist auch an Detlef's Antwort zu sehen. Ich brauche keine Fortran-Texte um eine Vektor-Addition zu implementieren. Ganz davon abgesehen dass kaum noch wer Fortran kann.
Die Komponenten des Vektors sind jetzt float. Wie kann man das variabel
machen sodass man float vec_t a und double vec_t b schreiben kann? Die
skalaren Rückgabewerte sollten dann auch entsprechend sein.
Ein Template draus machen. Dann wäre es vec_t<float> a und vec_t<double> b.
Ich würde das etwa so schreiben:
1
template<typenameT>
2
classvec_t
3
{
4
public:
5
constexprvec_t()=default;
6
constexprvec_t(Tx1,Ty1,Tz1)
7
:x{x1},y{y1},z{z1}
8
{}
9
10
private:
11
Tx{},y{},z{};
12
};
Den Copy-Konstruktor braucht es nicht. Der wird automatisch erzeugt.
Die ganzen Operatoren für double und int braucht es auch nicht. Man kann diese Typen auch ohne übergeben, und sie werden dann implizit konvertiert.
Für const mal vector und vector mal const definiere ich ja jeweils einen
eigenen Operator. Geht das auch eleganter?
Nein, aber da diese Multiplikation kommutativ ist, kannst du auch schreiben:
1
vec_toperator*(constvec_t&veca,constfloatval){
2
returnval*veca;}
Ich würde aber hier einfach den operator*= definieren und dann darauf basierend den operator*:
Das mit den "Millionen von Bibliotheken" ist auch nicht sonderlich
hilfreich. Selbst wenn man das macht: bis man sich da durchgewurschtelt
hat, hat man längst die technischen Sachen aus den Büchern
abgeschrieben. Das dauert nämlich nicht lange.
kannst du auch die Operatoren als Template implementieren, und es funktioniert solange die verwendeten Rechenschritte mit den Typen "U" und "T" ausführbar sind.
"a" ist vec_t<double>, die Addition behält den Typen von der linken Seite.
d.H "a+b+c" wird auch zu einem vec_t<double>, der sich ohne konvertierung in "z" speichern lässt.
Aber das geht nicht:
vec_t<double> z=(b+a+c);
"b+a+c" behält den typ von "b", also float-vector. den in z zu speichern erfordert eine Konvertierung.
Wenn du wirklich mit gemischten Typen arbeiten willst, ist es evtl. besser das nicht immer nach "Typ vom ersten Parameter gewinnt" zu machen.
z.B. mit Template-Spezialisierungen, die festlegen dass immer der größere Typ als Resultat rauskommt, double+float => double, float+double => double usw.
Naja, schau dir die Deklarationen der beiden Operatoren an. Die sind exakt gleich. Das wäre so, als ob du void foo(int x) mit void foo(int x) überladen willst. Das geht natürlich nicht.
Dein unärer Operator + darf keinen Parameter haben. Als input hat er ja schon das this-Objekt, das du in deiner Implementation gar nicht verwendest.
Dadurch bekommt der Ergebnis-Vektor automatisch den Typ der Element-Addition und das sollte immer der Größere sein (also vec_t<float> + vec_t<double> = vec_t<double>). Außerdem hat das den Vorteil, dass dieser Overload automatisch nicht berücksichtigt wird, wenn die Typen nicht addiert werden können (SFINAE), und man ggf. auch noch speziellere Overloads hinzufügen kann.
Außerdem:
1
template<typenameU>
2
vec_t(constvec_t<U>&v):x(v.x),y(v.y),z(v.z){}
Suggeriert dem Compiler dass alles in alles konvertierbar ist, und wenn nicht gibt es kryptische Fehlermeldungen (vermutlich insbesondere in Kombination mit der Addition). Wieder per SFINAE kann man diesen Konstruktor für nicht-konvertierbare Typen deaktivieren:
Naja, schau dir die Deklarationen der beiden Operatoren an. Die sind
exakt gleich. Das wäre so, als ob du void foo(int x) mit void foo(int x)
überladen willst. Das geht natürlich nicht.
Dein unärer Operator + darf keinen Parameter haben. Als input hat er ja
schon das this-Objekt, das du in deiner Implementation gar nicht
verwendest.
Ah, das verstehe ich.
Das geht jetzt syntaktisch durch
1
template<typenameT>
2
classvec_t
3
{
4
public:
5
typedefTelement_type;
6
Tx,y,z;
7
//vec_t () : vec_t (0, 0, 0) {} // Oder std::nan
8
vec_t(constT&x,constT&y,constT&z)
9
:x(x),y(y),z(z){}
10
11
template<typenameU>
12
vec_t(constvec_t<U>&v):x(v.x),y(v.y),z(v.z){}
13
14
template<typenameU>
15
vec_toperator+(){
16
returnvec_t(x,y,z);
17
}
18
template<typenameU>
19
vec_toperator+(constvec_t<U>&w){
20
returnvec_t(x+w.x,y+w.y,z+w.z);
21
}
22
};
Er meckert aber verhalten und ohne Error bei den beiden Operatorzeilen:
"template argument deduction/substitution failed"
Das aber scheitert:
vec_t<double> a(1,2,3);
vec_t<double> d= +a;
"no match for 'operator+' (operand type is 'vec_t<double>'
Was muss ich denn bei der Klasse ändern dass das durchläuft?
THX
Cheers
Detlef
"no match for 'operator+' (operand type is 'vec_t<double>'
Was muss ich denn bei der Klasse ändern dass das durchläuft?
Dein unary + Operator darf keinen Template-Parameter "U" nehmen.
Also einfach die template-Zeile davor rausnehmen.
Und die Compiler-Warnings anschalten, das erzählt der nämlich selbst..
couldn't deduce template parameter 'U'
Ja, vielen Dank, das geht jetzt. Der - Operator geht auch. Der * Operator soll das Skalarprodukt liefern. Wenn ich dem sage, dass er float zurückliefern soll geht das auch, siehe Code. Wie kann ich dem * Operator jetzt aber sagen, dass er mir den Typ der Eingangsvektoren zurückliefert?
Geht alles, wahrscheinlich. Die Syntax der typagnostischen Klasse für 3d Vektoren vec_t ist mir aber zu kryptisch und unschön um sie zu verwenden. Ausserdem liefert dabei norm von integer Vektoren irgendwas aber kein double oder float zurück. Wie würde das denn gehen?
Deswegen habe ich die Klasse für float 3D Vektoren zu fvec_t umbenannt und recycelt. Die werde ich verwenden, float reicht in meinem Fall. Beide Klassen habe ich getestet, naja, siehe code.
Sehr angenehm und ungewöhnlich auch dass der thread nicht verspamt oder vertrollt wurde.
Warum sind für fvec_t die Operationen als Funktionen implementiert anstatt als Methoden so wie im Klassentemplate.
Außerdem: Wenn man ein funktionierendes Vec<typename> hat, dann geht doch einfach
1
usingfvec_t=vec_t<float>;
oder nicht?
Was ich am Klassentemplate nicht so schön finde ist, dass Operatoren wie * nicht kommutatiiv sind, weil float * double einen double liefert, double * float hingegen nen float (bzw. Vektoren darüber).
Und das Zeug könnte man noch constexpr machen und auch Operatoren für Streams überladen.
Warum sind für fvec_t die Operationen als Funktionen implementiert
anstatt als Methoden so wie im Klassentemplate.
Hatte ich so implementiert irgendwo aus dem web gezogen. Gab da eine breite Einlassung mit 'friends', lag weit jenseits meines jetzigen C++ Horizonts.
Ich benötige für umfangreiche Vektoralgebra eine klare Syntax, weil ich sonst die Fehler nicht finde. Das funktioniert mit fvect_t besser als mit vec_t. Syntax mit vect_t ist total kontraintuitiv und zerrissen, da könnte ich auch so weitermachen wie bisher mit addvec(a,b) für die Addition zweier Vektoren als structs.
Kannst Du mal ein Beispiel machen wie für fvec_t Operatoren in der Klasse implementiert werden?
Außerdem: Wenn man ein funktionierendes Vec<typename> hat, dann geht
doch einfach
1
usingfvec_t=vec_t<float>;
oder nicht?
Keine Ahnung, null Schimmer was using bedeutet und was Du tun willst. Kannst Du ein Beispiel posten?
Was ich am Klassentemplate nicht so schön finde ist, dass Operatoren wie
nicht kommutatiiv sind, weil float * double einen double liefert,
double * float hingegen nen float (bzw. Vektoren darüber).
Ja, Schönheit und Kommutativität. Man benötigt verschiedene Operatoren für constvec und vecconst. Das ist weder schön noch kommutativ. Aber wenigstens funktionierts'.
Und das Zeug könnte man noch constexpr machen und auch Operatoren für
Streams überladen.
??. Benötige auch ein Beispiel. Was ist ein stream im C++ Kontext?
Hatte ich so implementiert irgendwo aus dem web gezogen. Gab da eine
breite Einlassung mit 'friends', lag weit jenseits meines jetzigen C++
Horizonts.
friend ist eigentlich recht simpel. Wenn man eine freistehende Funktion schreiben will, die direkt auf Members einer Klasse zugreifen können soll, die eigentlich private sind, dann kann man das erreichen, indem man die Funktion zu einem friend der Klasse macht. Das kann gerade bei den Operatoren hilfreich sein.
Ich benötige für umfangreiche Vektoralgebra eine klare Syntax, weil ich
sonst die Fehler nicht finde. Das funktioniert mit fvect_t besser als
mit vec_t.
Wieso? Die ist doch eigentlich gleich. Und letztendlich war es ja deine Anforderung, für jede Instanz den Elementtyp wählen zu können. Wenn es sowieso am Ende immer nur float sein soll. kannst du dir die Templaterei auch ganz sparen.
Syntax mit vect_t ist total kontraintuitiv und zerrissen, da
könnte ich auch so weitermachen wie bisher mit addvec(a,b) für die
Addition zweier Vektoren als structs.
Hast du mal ein Beispiel, wo die im Vergleich zu deinem fvect "zerrissen" ist?
Kannst Du mal ein Beispiel machen wie für fvec_t Operatoren in der
Klasse implementiert werden?
Was brauchst du da für ein Beispiel? Es funktioniert ohne Template genau gleich wie mit Template, und da hast du es doch hinbekommen.
Außerdem: Wenn man ein funktionierendes Vec<typename> hat, dann geht
doch einfach
using fvec_t = vec_t<float>;
oder nicht?
Keine Ahnung, null Schimmer was using bedeutet und was Du tun willst.
Ein Typ-Alias erstellen. Das ist das gleiche wie:
1
typedefvec_t<float>fvect_t;
Es führt dazu, dass du vec_t<float> auch als fvec_t benutzen kannst.
Was ich am Klassentemplate nicht so schön finde ist, dass Operatoren wie
nicht kommutatiiv sind, weil float * double einen double liefert,
double * float hingegen nen float (bzw. Vektoren darüber).
Ja, Schönheit und Kommutativität. Man benötigt verschiedene Operatoren
für constvec und vecconst. Das ist weder schön noch kommutativ. Aber
wenigstens funktionierts'.
Wegen solchen Asymmetrien finde ich es übrigens besser, die Operatoren wenn möglich nicht in der Klasse, sondern als freie Funktionen zu implementieren. Bei den Konvertierungen gibt es sonst an machen Stellen unterschiedliche Behandlung des linken und rechten Operanden, weil der linke das this-Objekt ist. Bei einer freien Funktion sind sie gleichwertig.
Und das Zeug könnte man noch constexpr machen und auch Operatoren für
Streams überladen.
??. Benötige auch ein Beispiel. Was ist ein stream im C++ Kontext?
std::cin und std::cout sind z.B. Streams.
constexpr kann man verwenden, damit Berechnungen mit Konstanten auch komplett zur Compilezeit durchgeführt und deren Ergebnisse als echte Compilezeit-Konstanten verwendet werden können.
https://jblevins.org/log/efficient-code
Die verschachtelte Schleife ist jetzt kein Fortran oder C oder C++ - Ding, das ist einfach eine Know How-Grundlage u.a. aus der Grafik-Programmierung.
Ja, habe jede Menge C++ Bücher, von Stroustrup angefangen. Ich dachte ich lerns' nicht mehr, aber die Operatoren für complex und jetzt für 3d Vektoren machen C++ schon sehr attraktiv. Das geht in C nich. Ich bin möglicherweise in der Lage sowas für Matrizen zu basteln, das wäre dann sehr nützlich. Ich kenne IT++ und Genossen, aber das war mir immer zuviel Overhead, das benötige ich nicht und krieg es auch nicht auf den uC.
Achso, das Ding steht in der Klasse selbst. Das funzt.
Meine anderen Operatoren stehen immer ausserhalb der Klasse.
Wie krieg ich das denn ähnlich hin?