template-interface auf virtuell mappen
-
Ich habe einen Satz funktoren, die alle einen operator() implementieren, z.b.
struct match_space { bool operator()(char ch) const { return isspace(ch); } }; struct match { char c; match(char c_) : c(c_) {} bool operator()(char ch) const { return ch == c; } };
Diese verwende ich in template-methoden, z.B.
template <typename MATCH> void x::find(MATCH const & m) { ... } // verwendung: x.find(match('x'));
soweit klappt alles wie es soll.
Nun möche ich aber für eine bestimmte Gruppe von Funktionen das template über MATCH vermeiden (bei diesen ist die performance von match() weniger wichtig, und templates würden den generierten Code ganz schön aufblähen.)ich versuche also, eine Klasse zu basteln, bei der ich einen beliebigen match-Functor angeben kann, die das aber in einen virtuellen Aufruf verpackt. Krieg' abver nichts ganzes hin. Etwa so:
/// Basisklasse für aufruf über VMT struct vmatch_base { virtual bool operator()(TCHAR ch) = 0; } template <class MATCH> struct vmatch_impl : public vmatch_base { ??? } // Deklaration etwa so void x::vfind(vmatch_base const & match) {...} // und aufruf am liebsten x.vfind(match('x'));
Dafür müßte ich aber (glaub ich) von vmatch_base jeweils eine Klasse für jeden match-Functor mit entsprechendem Constructor ableiten. Krieg ich das auch einfacher him, mit einer Template-Implementation von vmatch_impl hin, ohne auf total häßlichen Syntax ausweichen zu müssen?
(was ich hinbekomme ist entweder unter Angabe der template-spezialisierung -x.vfind( vmatch_impl<match>('x') )
oder mit Hilfsfunktion
x.vfind( vmatch(match('x')))
ist aber beides ziemlich haarig, besonders bei komplizierteren match'es
Irgendwelche Ideen / Tricks?
(Danke schon mal für's lesen
)
-
dir geht es nur um die find methode, die nicht ständig template-instanziiert werden soll?
also, zuerst analyse. du hast natürlich das problem, dass du nichts am aussehen des aufruf von vfind ändern möchtest. d.h. der benutzer von vfind soll nicht wissen, dass vfind in wirklichkeit gar kein template ist.
deine funktoren müssen daher irgendwie in ein vmatch_base umgewandelt werden.
die funktoren willst du klarerweise auch nicht ändern.
d.h. du brauchst eine klasse, abgeleitet von vmatch_base, die jeden funktor als konstruktorargument annehmen kann//die vereinfachung sollte kein problem für dich darstellen, vermute ich mal struct functor { void operator () () { /* ... */ } }; struct vmatch_base { virtual void operator () () = 0; }; //erster ansatz template <class T> struct vmatch_impl { vmatch_impl (T const&) { /* ... */ } };
aber moment, damit gehen zuviele herumkonvertierungen einher
vfind (functor()); //geht nicht, da keine konvertierung von functor nach vmatch_base
und vfind kannst du natürlich nicht so implementieren, dass es ein vmatch_impl als template erwarten würde (dann wäre vfind ja wieder ein template)
template <class T> void vfind (vmatch_impl<T> const&); //nein //gedankengang 1: erlaube implementierung vfind(vmatch_impl<match>('x')) //vermutlich durch vmatch_impl<T>::vmatch_impl(T const&); //denn du weißt, dass mit diesem konstruktor so ziemlich alle konvertierungen möglich sind. //das willst du nicht.
jetzt ist natürlich bekannt, dass funktionen ihre template argumente herleiten können.
template <class T> void foo (T const&) { } foo (4); //T ist int foo<int*> (0); //T ist int* //gedankengang 2: //mach eine funktion vmatch, die vermutlich so aussieht /* template <class T> vmatch_impl<T> vmatch (T const& t) { return vmatch_impl<T>(t); } */ //aber damit sind wir immer noch nicht da, wo du hinwillst
also, zusammenfassend: du brauchst
(1) eine funktion, die das herleiten eines vmatch_impl<T> für T ermöglicht
(2) das ganze als konstruktor, damit automatische konvertierungen möglich sindclass vmatch_base { virtual void operator () () = 0; }; class vmatcher { public: template <class T> vmatcher (T const& t) { //? } void operator () () { //? } }; //damit ist möglich vfind (vmatcher &); vfind (functor());
da stellt sich nun ein neues problem. nur vmatchers konstruktor ist ein template. das dem konstruktor übergebene argument soll aber noch in operator() verwendet werden.
wahrscheinlich fällt dir außerdem auf, das vmatch_base jetzt nicht notwendig ist.class vmatcher { public: template <class T> vmatcher (T const& t) { //? } void operator () () const { //? } }; //damit ist möglich void vfind (vmatcher const &); vfind (functor());
wir müssen jetzt das ganze schon irgendwo von template auf virtuell mappen, denn offensichtlich haben wir in operator() von vmatcher ja keine informationen mehr über den richtigen funktor. also wieder rein mit der vmatch_base.
class vmatch_base { virtual void operator () () = 0; }; class vmatcher { public: vmatch_base *helper; template <class T> vmatcher (T const& t) { //? //helper = new T(t) ? } void operator () () const { (*helper)(); } }; //damit ist möglich void vfind (vmatcher const &); vfind (functor());
aha, wir kommen der sache schon näher. folgendes problem tritt auf:
der konstruktor hat jetzt die aufgabe, dass t zu einem vmatch_base zu machen. da der funktor entweder als referenz übergeben wird, oder nicht - und sich damit sein gültigkeitsbereich nicht über den ctor hinaus erstrecken würde - müssen wir mit new arbeiten.
gedankengang 3:
gib den funktoren je die basisklasse vmatch_base. damit ist so eine konvertierung im ctor von vmatch möglich und einfach.struct vmatch_base { virtual void operator () () = 0; virtual ~vmatch_base () {} }; struct functor : vmatch_base { void operator () () { /* ... */ } }; class vmatcher { public: vmatch_base *helper; template <class T> vmatcher (T const& t) { helper = new T(t); //speichermanagement überlass ich dir ;) } void operator () () const { (*helper)(); } }; //damit ist möglich vfind (vmatcher const &); vfind (functor());
problem:
änderung an den funktoren von vorneherein ausgeschlossen. die funktoren bekommen alle eine vtable hineingedrückt. außerdem schwierig, wenn die funktoren nicht nur void operator() () besitzen (aber dazu später)
das bedeutet, vmatchers konstruktor muss einen anderen weg finden, die funktoren in ein vmatch_base zu stecken.
machen wir einfach eine weitere klasse, die sich elegant um einen funktor wrappen kannstruct functor { void operator () () { /* ... */ } }; struct vmatch_base { virtual void operator () () = 0; virtual ~vmatch_base () {} }; template <class Functor> struct vmatch_helper { Functor f; //wir brauchen ja eine kopie, nicht vergessen. vmatch_helper (Functor const& f) : f(f) {} void operator () () { f(); } }; class vmatcher { public: vmatch_base *helper; template <class T> vmatcher (T const& t) { helper = new vmatch_helper<T>(t); //speichermanagement überlass ich dir ;) } void operator () () const { (*helper)(); } }; //damit ist möglich vfind (vmatcher const &); vfind (functor());
hm, damit wären einmal die grundsätzlichen anforderungen erfüllt, oder bist du böse, das vmatcher jetzt keine basisklasse mehr hat
dazu wäre noch zu sagen, dass letzteres zwar relativ gut funktioniert, allerdings von der signatur void operator() () abhängt. versuch mal, den rückgabewert zu ändern. oder besser noch die parameteranzahl. das herumgewurschtele mit dem templatelosen vmatcher und vmatch_base vermießt da natürlich einiges. wenn du in der hinsicht flexibler sein willst, muss du wohl auf gedankengänge 1-3 zurückgreifen. eventuell lässt sich auch mit boost::any noch ein bisschen was dran herumdrehen.
sorry, besser krieg ich's im moment nicht hin. ist aber auch ziemlich spät :xmas1: