templates, implizite konvertierung
-
hallo,
ich habe folgende klasse
(in etwa)template <class T> class Vector { }kann mir wer sagen, wie ich eine implizite konvertierung von
folgenden vectoren schreiben kann? (und zwar nur für diese)int -> double
float -> doubleusw, also eigentlich für alle, bei denen keine information verloren geht.
Ich weiss das man es mit überschreibung von operator() machen müsste,
aber wegen der template sache bekomme ich das irgendwie nicht hin
-
Da mußt du Spezialisierungen für Vector<double> schreiben, die diese Konvertierungen anbieten.
Wenn die Klasse viel Code hat, würde ich den gemeinsamen Anteil seperat halten - Template-Spezialisierungen "erben" nicht die Methoden der Grundversion:
template<typename T> class Vector_base { ... }; template<typename T> class Vector : public Vector_base<T> { //muß nur die Ctor'en überschreiben - darum kommst du nicht herum }; template<> class Vector<double> : public Vector_base<double> { public: //Umwandlungs-Konstruktoren: Vector(const Vector<int>&); Vector(const Vector<float>&); //außerdem noch die allgemeinen Konstruktoren aus der Vector<> };
-
CStoll schrieb:
Da mußt du Spezialisierungen für Vector<double> schreiben, die diese Konvertierungen anbieten.
Wenn die Klasse viel Code hat, würde ich den gemeinsamen Anteil seperat halten - Template-Spezialisierungen "erben" nicht die Methoden der Grundversion:
...Das ist eine Möglichkeit, man endet aber trotzdem mit einer großen Menge an Duplizität, die nichts mit dem Problem zu tun hat. Besser ist die eigentliche Konvertierung außerhalb der Klasse durchzuführen, irgendwelche Spezialisierungen führen dann nicht zu extra Aufwand im Vektor-Temnplate. Was wir brauchen, ist eine Konvertierungsfunktion, die verschiedene Vektoren als Argument nimmt, wobei bestimmte zusätzliche Bedingungen an den Templateparameter gestellt werden:
template<typename T> class Vector { template<typename U> Vector(const Vector<U>& other); }Für ungültige Kombinationen aus T und U soll diese Konvertierung unterdrückt werden. Dabei haben wir zwei Möglichkeiten:
1. wir lassen die Funktion so stehen, und machen sie für ungültige Paarungen nicht instantiierbar
2. wir unterdrücken solche Paarungen mit SFINAE
Variante 1 ist zweifellos verständlicher für den Templateschreiber, hat aber den Nachteil, das der Vektor dann immer noch als konvertierbar für solche Paarungen gilt (is_convertible und ähnliche Metatemplatefunktionen machen dann Ärger).Zum Eingemachten: wir verlagern die eigentliche Konvertierung in ein eigenes Template und spezialisieren geeignet:
template<typename T, typename U> struct Convert { static const bool value = true; T convert(U x) const { return static_cast<T>(x); } }; template<typename T, typename U> struct SafeConvert { static const bool value = false; }; // jetzt die Spezialisierungen, das ist immer noch eine Menge Arbeit template<> struct SafeConvert< int, long > : Convert< int, long > {}; template<> struct SafeConvert< int, double > : Convert< int, double > {}; template<> struct SafeConvert< float, double > : Convert< float, double > {}; // usw. template<typename T> template<typename U> Vector<T>::Vector(const Vector<U>& other) { // für jedes einzelne Element SafeConvert<T,U>().convert aufrufen... }Das Ganze mit SFINAE wird nur wenig komplizierter (da ich SafeConvert schon vorbereitet habe):
template<typename T> class Vector { template<typename U> Vector(const Vector<U>& other, typename enable_if<SafeConvert<T,U> >::type* = 0); }Die Wiederholungen beschränken sich jetzt auf die Nennung aller gültigen Paarungen. Aber auch das ist eigentlich unnötiger Aufwand und skaliert nicht gut, insbesondere, wenn das Ganze für eigene Typen erweiterbar sein soll.
Mittels std::numerc_limits können wir das besser machen:
Ein Konvertierung ist sicher, wenn:
- beide Typen integer sind, und beide vorzeichenbehaftet sind und der Zieltyp nicht weniger Stellen besitzt, oder
- beide Typen integer sind, und die Quelle vorzeichenlos ist und der Zieltyp nicht weniger Stellen besitzt, oder
- die Quelle ein Integer und das Ziel ein Gleitkommatyp ist, und der der Gleitkommatyp hinreichen viele signifikante Stellen besitzt, oder
- beide Gleitkommatypen sind, und der Zieltyp in signifikanten Stellen und Stellen der Mantisse nicht geringer als die Quelle ist:template<typename T, typename U> struct SafeConvert { typedef std::numeric_limits<T> src; typedef std::numeric_limits<U> dest; static const bool value = src::is_specialized && dest::is_specialised && src::radix == dest::radix && ( ( src::is_integer && dest::is_integer && src::digits <= dest::digits && !( src::is_signed && !dest::is_signed ) ) || ( src::is_integer && !dest::is_integer && src::digits <= dest::digits ) || ( !src::is_integer && !dest::is_integer && src::digits <= dest::digits && src::min_exponent >= dest::min_exponent && src::max_exponent <= dest::max_exponent ) ) T convert(U x) const { return static_cast<T>(x); } };Das kann man noch etwas besser machen (ich war jetzt zu faul, mit unterschiedlichen Darstellungen (radix) und exakten gebrochenen Zahlen (rational) oder erweiterten Zahlenbereichen (complex) umzugehen. Zweifellos ist es so aber am wenigsten Schreibarbeit - erfordert aber eben erst einmal ein bisschen nachdenken statt stupides C&P
