Template Argument von Template Parameter herausfinden
-
Hallo,
ich programmiere mir gerade eine Bibliothek, die unter Anderem allgemeine gewöhnliche Differentialgleichungen numerisch mittels verschiedener Verfahren lösen können soll. Um damit dann Bewegungsgleichungen lösen zu können, möchte ich das symplektische Euler-Verfahren implementieren, da dieses ja weitgehend keinen (bzw. einen recht geringen) Energy-Drift erzeugt. Bis jetzt habe ich die üblichen klassischen Verfahren ohne Probleme mittels Template-Klassen implementiert, beim symplektischen Euler habe ich jedoch ein syntaktisches Problem mit den Template-Klassen.Zunächst habe ich eine abstrakte Klasse "ODE" erstellt. Diese ist das Basisinterface von der jede Differentialgleichung erben muss. Sie ist als Funktor designt.
template<typename T> // Class for reprisentation of ODEs of the type y' = f(t, y) struct ODE { ODE() {}; virtual ~ODE(void) {}; // Return the right hand side of the ODE y' = f(t, y) virtual T operator()(double t, T& y) = 0; };Wie zu erkennen, bildet diese Klasse nur eine Differentialgleichung 1. Ordnung ab. Ich muss aber auch Differentialgleichungen 2. Ordnung lösen können. Der mathematische Ansatz ist dabei wie folgt:
Eine Differentialgleichung der Form kann immer in ein System von Differentialgleichungen 1. Ordnung überführt werden. Für eine Differentialgleichung 2. Ordnung sähe das dann wie folgt aus:Es bleibt also nurnoch eine vektorielle Differentialgleichung 1. Ordnung zu lösen oder salopp: Eine Differentialgleichung 2. Ordnung ist eine vektorielle Differentialgleichung 1. Ordnung! Dieses Verhalteung wurde in folgender Klasse implementiert:
template<typename T> // Class for representation of a second order ODEs of the type // y' = f(t, y, y'), y'' = g(t, y, y'), which is the same as: // (y', y'') = u(t, (y, y')) struct SecondOrderODE : public ODE<Vector<2, T>> { SecondOrderODE() {}; virtual ~SecondOrderODE() {}; // Return the value of the first derivative virtual T f(double t, Vector<2, T>& y) = 0; // Return the value fo the second derivative virtual T g(double t, Vector<2, T>& y) = 0; // Return the value of the first derivative of the system vector Vector<2, T> operator()(double t, Vector<2, T>& y) { return Vector<2, T>(f(t, y), g(t, y)); } };Vector<int N, typename T>ist hierbei eine naive Implementierung eines N-dimensionalen Vektors mit Elementen vom Typ T.Somit können nun gewöhnliche Differentialgleichungen 1. und 2. Ordnung jeglicher Art abgebildet werden. Als nächstes war der Integrator zu implementieren. Auch hierfür wurde zunächst eine abstrakte Klasse angelegt, welche allgemein einen Integrator beschreibt:
template<typename T> class Integrator { public: Integrator(ODE<T>* ode, double t, double h, T* y) : ode_(ode), t_(t), h_(h), y_(y) {}; virtual ~Integrator() {}; // Calculate the required values of the ODE virtual void calculate() = 0; // Apply the calculated step. Call calculate() before calling apply()! virtual void apply() = 0; protected: // The ODE which should be integrated ODE<T>* ode_; // Size of a single step double h_; // Current value of the variable double t_; // Current value of the integrand T* y_; };Nun das Problemkind: der symplektische Euler. Der symplektische Euler ist zwar eine ODE, lässt jedoch aber nur Differentialgleichungen 2. Ordnung zu. Das Problem ist folgendes:
SecondOrderODEist eineODE<Vector<2,T>>
DerIntegratorerhält dann als Template Argument folglich auchVector<2,T>, da er ja eine ODE über diesem Typ lösen soll.
Mein Problem fängt nun schon beim Konstruktor vonIntegratorSymplecticEuleran, denn ich bräuchte etwas in der Art:template<class V = Vector<2, T>> class IntegratorSymplecticEuler : Integrator<V> { public: IntegratorSymplecticEuler(SecondOrderODE<T> *ode, double t, double h, V* y) {} };Ich muss also bestimmen können welchen Datentyp die Vektoren beinhalten.
Hier weiß ich jedoch nicht, wie dies zu formulieren ist. So wie angegeben kompiliert es ja offensichtlich nicht. Oder hat evtl. jemand eine Idee dies schlauer auf eine andere Art zu lösen?Danke schonmal und habt bitte ein bisschen Nachsicht, das ist mein erster Beitrag hier und ich bin noch einigermaßen blutiger Anfänger

-
Ich habe gerade mal zwei Zeilen deines Problems gelesen und glaube jetzt schon zu wissen, dass du eine partielle Spezialisierung brauchst.
-
// Primärtemplate template<class T> class IntegratorSymplecticEuler; // Die partielle Spezialisierung - die Template-Argumente (hier T) werden per function template argument deduction deduziert template<class T> class IntegratorSymplecticEuler<Vector<2, T>> : Integrator<Vector<2, T>> { public: IntegratorSymplecticEuler(SecondOrderODE<T> *ode, double t, double h, Vector<2, T>* y) {} };?
-
template<class T, class V = Vector<2, T>> class IntegratorSymplecticEuler : Integrator<V> { public: IntegratorSymplecticEuler(SecondOrderODE<T> *ode, double t, double h, V* y) {} };Vielleicht meinst du sowas?
-
Vielen Dank für die schnelle Antwort. Der Mechanismus der "function template argument deduction" war mir bist jetzt nicht bekannt. Könntest du mir schnell in ganz gröber Zügen erklären was der genau macht? Bin ja wie gesagt noch Anfänger
Aber damit kompiliert mein Test zumindest schonmal. Das ist sehr gut.
-
Skym0sh0 schrieb:
template<class T, class V = Vector<2, T>> class IntegratorSymplecticEuler : Integrator<V> { public: IntegratorSymplecticEuler(SecondOrderODE<T> *ode, double t, double h, V* y) {} };Vielleicht meinst du sowas?
Deine Lösung funktioniert zwar, bringt mich aber leider nicht zum Ziel.
IntegratorSymplecticEulersoll ja immernochODEs vom Typ z.B.ODE<Vector<2, double>>lösen.
Ich müsste somit Aufrufe wie folgt machen:Integrator<Vector<2, double>>* i = IntegratorSymplecticEuler<Vector<2,double>>(...)Danke trotzdem

-
S1cknessGER schrieb:
Der Mechanismus der "function template argument deduction" war mir bist jetzt nicht bekannt.
Das ist nicht der Mechanismus der hinter dem Code steckt. Der heißt nämlich "Partielle Spezialisierung" (engl. partial specialization). Partielle Spezialisierungen werden eingesetzt, um bei Klassentemplates Template-Argumente herauszufiltern.
Partielle Spezialisierungen können auf eine Reihe von Template-Argumenten zutreffen:
#include <iostream> #include <type_traits> template <typename T, typename = void> struct A { static void f() { std::cout << "A - Primaertemplate\n"; } }; template <typename T> struct A<T, typename std::enable_if<std::is_integral<T>::value>::type> { static void f() { std::cout << "A - Partielle Spezialisierung\n"; } }; int main() { A<float>::f(); A<int>::f(); A<long>::f(); }Wenn du eine Spezialisierung eines Templates - wie
A<long>- zum ersten Mal anforderst, dann muss der Compiler die Spezialisierung dieses Templates instantiieren.
Nun muss er aber erst herausfinden, welches Template er für diesen Prozess - genannt Template-Instantiierung (engl. template instantiation) - nimmt.Wenn es nur ein Primärtemplate gibt, dann nutzt er das Primärtemplate.
Wenn es irgendeine explizite Spezialisierung gibt, die genau die gleichen Argumente hat wie die gegebene Spezialisierung, dann wird diese genommen.
Gibt es aber ein oder mehrere partielle Spezialisierungen, werden imaginäre Funktionstemplates verwendet, um zu entscheiden aus welchem Template nun die Spezialisierung instantiiert wird.In obigem Fall wäre das nur eines, also
template <typename T> void f( A<T, typename std::enable_if<std::is_integral<T>::value>::type> );Nun wird zuerst überprüft ob die partielle Spezialisierung überhaupt ein match ist (sprich zutrifft), indem man ein Argument vom Typ
A<int>an dieses imaginäre Funktionstemplate übergibt.
Falls die Deduzierung der Template-Argumente fehlschlägt, wird also das Primärtemplate genommen.
Falls die Deduzierung aber erfolgreich ist, dann sind wir fertig und instantiieren die Spezialisierung mit der partiellen Spezialisierung (und zwar mit den deduzierten Argumenten als Template-Argumenten - hier sind die deduzierten Argumente die selben,int).Nun gibt es einen Mechanismus in der Template-Meta-Progammierung, der im Beispielcode zu demonstrativen Zwecken eingesetzt wird - SFINAE. Es geht aber über den Rand dieses Posts hinaus, letzteres auch noch zu erklären.
Wichtig ist nur, dass die Deduzierung erfolgreich ist und die (einzige) partielle Spezialisierung ausgewählt wird.
-
Danke, das hat mir geholfen.
Partielle Spezialisierung kannte ich zum Teil schon, aber noch nicht sooo gut.
Ich denke demnächst gibt's dann erstmal ein wenig Literatur zu Templates