Templates: Partielle Spezialisierung oder Vererbung
-
Hallo,
ich habe schreibe gerade an einer generischen Vektorklasse für eine Mathebibliothek. Dabei soll es eine Template Klasse geben, die über einen Typen und die Anzahl der Komponenten im Vektor parametrisiert werden kann.
Zum Beispiel so:
Vector<float, 3> v1; Vector<int, 8> v2;Dies sieht bei mir so aus: (Ich habe die Klasse auf die für das Problem relevanten Teile reduziert.)
template <typename T, int size> class Vector { public: Vector(); Vector<T, size>& operator = (const Vector<T, size>& src); Vector<T, size> operator + (const Vector<T, size>& src) const; protected: T vec[size]; //!< Speicher für die Werte };Jetzt möchte ich für bestimmte Vektoren (2- und 3-dinemsionale) zusätzliche Konstruktoren und Methoden hinzufügen.
Dafür habe ich zunächst folgenden naiven Ansatz gewählt:
template <typename T> class Vector2D : public Vector<T, 2> // Korrektur { public: Vector2D(); Vector2D(T x, T y); Vector2D<T>& operator = (const Vector<T, 2>& src); };Folgender Ausdruck führt so aber zu einem Fehler:
Vector2D<float> v1, v2, v3; v3 = v1 + v2;Der Fehler ist hierbei, dass v1 + v2 zu einem Vector<float, 2> ausgewertet wird und v3 aber ein Vector2D<float> ist, was einen impliziten Downcast zu folge hätte.
Warum greift an dieser stelle nicht mein Vector2D::operator=?
Eine Möglichkeit das o.g. problem zu lösen, wäre natürlich die Operatoren (hier nur +) aus Vector nochmal in Vector3D zu implementieren, was ich, aufgrund der Codeverdopplung, als unschön ansehe.
Eine andere Möglichkeit, von der ich gelesen habe ist, die partielle Spezialisierung von Template Klassen. Dies habe ich mal folgendermaßen probiert:
template<typename T> class Vector<T, 2> { public: Vector(T x, T y); };Leider erfüllt dieser Code so auch nicht seinen Zweck, da Vector<T, 2> die Ursprungsklasse für den Fall size=2 komplett ersetzt und nicht nur ergänzt.
Nun zur meiner Abschließenden frage:
Was ist der beste weg eine generische Klasse zu benutzen um speziellere Unterklassen zu erzeugen, bei denen nicht alle operatoren neu implementiert werden müssen?
Vielen Dank für die Hilfe.
kie
-
kie schrieb:
Der Fehler ist hierbei, dass v1 + v2 zu einem Vector<float, 2> ausgewertet wird und v3 aber ein Vector2D<float> ist, was einen impliziten Downcast zu folge hätte.
Warum greift an dieser stelle nicht mein Vector2D::operator=?
Weil du nach dem
operator+einVector<float, 2>hast und nicht einVector2D.kie schrieb:
Was ist der beste weg eine generische Klasse zu benutzen um speziellere Unterklassen zu erzeugen, bei denen nicht alle operatoren neu implementiert werden müssen?
kie schrieb:
Jetzt möchte ich für bestimmte Vektoren (2- und 3-dinemsionale) zusätzliche Konstruktoren und Methoden hinzufügen.
Spezifiziere dies bitte genauer, dann kann man auch besser helfen.
Es gäbe zum Beispiel die Möglichkeit von freien Funktionen. Oder man kann über Zwischentypen gehen. Allerdings frage ich mich sowieso, wieso ein
Vector2Dplötzlich eine andere Schnittstelle haben sollte wie einVector3D. Sowas verwirrt eher, als dass es hilfreich ist.Grüssli
-
Dravere_logoff schrieb:
Weil du nach dem
operator+einVector<float, 2>hast und nicht einVector2D.Aber mein
operator=ist mit einemVector<float,2>als r-value definiert, ändert das nichts?Dravere_logoff schrieb:
Spezifiziere dies bitte genauer, dann kann man auch besser helfen.
Es gäbe zum Beispiel die Möglichkeit von freien Funktionen. Oder man kann über Zwischentypen gehen. Allerdings frage ich mich sowieso, wieso ein
Vector2Dplötzlich eine andere Schnittstelle haben sollte wie einVector3D. Sowas verwirrt eher, als dass es hilfreich ist.1. Vector2D und Vector3D werden in meinem Programm sehr oft benutzt und sollen deshalb komponentenweise Konstruktoren haben.
2. Ein Vector3D soll auch das Kreuzprodukt implementieren, welches nur für 3-dimensionale Vektoren definiert ist.
Gruß
-
kie schrieb:
Aber mein
operator=ist mit einemVector<float,2>als r-value definiert, ändert das nichts?Ist schwer zu sagen, weil du so viele Schreibfehler in deinem Code hast. Du schreibst zum Teil von 3D, dann ist es wieder 2D, der
operator=erwartet zum Beispiel einVector<float, size>und nicht einVector<float, 2>, ich bin einfach davon ausgegangen, dass du einen normalenoperator=implementiert hast
Zum Rest komme ich später noch oder jemand anderes, gerade keine Zeit mehr

Grüssli
-
Dravere_logoff schrieb:
Ist schwer zu sagen, weil du so viele Schreibfehler in deinem Code hast. Du schreibst zum Teil von 3D, dann ist es wieder 2D...
Hab einen Fehler korrigiert.
Der Code bezieht sich nur auf Vector2D, da die Idee für Vector3D analog ist. Jedoch möchte ich beides implementieren, weshalb ich im Text von beidem spreche.
Ich hoffe, nun sind gewisse Verwirrungen beseitigt.
-
kie schrieb:
Hab einen Fehler korrigiert.
Den anderen dafür nicht. In vector2D ist size nicht bekannt, was soll also der
Vector2D<T>& operator = (const Vector<T, size>& src);wirklich sein?Ich würd vermutlich eine Erzeugendenfunktion machen und die Ableitung weglassen, wenns wirklich nur um die Konstruktion geht:
template <class T> Vector<T, 2> makeVector2D(T const& t1, T const& t2) { Vector<T, 2> tmp; /* Werte belegen */ return tmp; }
-
pumuckl schrieb:
kie schrieb:
Hab einen Fehler korrigiert.
Einen anderen dafür nicht. In vector2D ist size nicht bekannt, was soll also der
Vector2D<T>& operator = (const Vector<T, size>& src);wirklich sein?Danke für den Hinweis. Habs korrigiert. Size muss da durch eine 2 ersetzt werden, damit es mathematisch Sinn macht!
-
kie schrieb:
pumuckl schrieb:
kie schrieb:
Hab einen Fehler korrigiert.
Einen anderen dafür nicht. In vector2D ist size nicht bekannt, was soll also der
Vector2D<T>& operator = (const Vector<T, size>& src);wirklich sein?Danke für den Hinweis. Habs korrigiert. Size muss da durch eine 2 ersetzt werden, damit es mathematisch Sinn macht!
Danke für die Antwort:
Es geht nicht nur um die Konstruktion, sondern auch darum, dass ein Vector3D das Kreuzprodukt hat und der Vector2D nicht.
-
kie schrieb:
Es geht nicht nur um die Konstruktion, sondern auch darum, dass ein Vector3D das Kreuzprodukt hat und der Vector2D nicht.
Wenn ich mal davon ausgehen kann dass du entsprechende Getter für die einzelnen Elemente hast, ist das Kreuzprodukt sowieso eine freie Funktion. Immernoch kein Grund für eine eigene Klasse also

-
Ich habe auch so ein Klassen-Template ("point") in meiner Sammlung. Die Initialisierung könnte man mit "SFINAE" lösen, aber das ist wirklich Overkill. Stattdessen habe ich mir make_point-Funktionen gebaut:
template<typename T> inline point<T,2> make_point(T const& x, T const& y) { [...] } template<typename T> inline point<T,3> make_point(T const& x, T const& y, T const& z) { [...] } template<typename T> inline point<T,4> make_point(T const& x, T const& y, T const& z, T const& w) { [...] }Das ist genauso effizient im Vergleich zu einem Kontruktor (bei einem guten Compiler) und kann dir die Benennung des Typs ersparen.
-
Es gäbe im übrigen noch die Möglichkeit das Klassentemplate als POD zu implementieren. Dann kann man folgendes schreiben:
typedef Vector<float, 3> Vec3f; Vec3f vec = { 1.f, 2.f, 3.f };Kreuzprodukt würde ich auch über eine freie Funktion implementieren.
Grüssli