Implementierung eines statischen Visitors
-
Nexus schrieb:
Nein, die Klassenhierarchie steht fest und kann nicht verändert werden.
Da fällt mir was ein... Wenn man virtuelle Funktionen ausserhalb der Klasse benötigt, ist Dynamic-Dispatch eine Möglichkeit. Allerdings müsste ich dann die einzelnen
Visit()-Funktionen irgendwo registrieren.Aber das ist wahrscheinlich das kleinste Übel...

-
hmm... Ich überleg noch

Mal eine andere Frage:
Weswegen übergibst du den 'genauen' Typ (oder Basisklasse) vonBase* base;nicht per Template-Parameter an?
Ein Visitor soll ja eh einen bestimmten base-Typ (oder dessen Basisklasse)
behandeln können, oder?Welche Informationen bestehen über Base*?
Ansonsten wäre es ja als würde man einen void-Zeiger übergeben (abgesehen von
dem virtuellen Destruktor)
-
XSpille schrieb:
Mal eine andere Frage:
Weswegen übergibst du den 'genauen' Typ (oder Basisklasse) vonBase* base;nicht per Template-Parameter an?
Das tue ich in Wirklichkeit, ist hier der Einfachheit weggelassen. Zum Beispiel sind in meinem Code auch durchgehend ungekapselte
structs zu sehen.XSpille schrieb:
Welche Informationen bestehen über Base*?
Ansonsten wäre es ja als würde man einen void-Zeiger übergeben (abgesehen von
dem virtuellen Destruktor)Naja,
Baseist halt die polymorphe Basisklasse, die als Abstraktion der konkreten abgeleiteten Klassen dient. Eventuell muss sie später auch RTTI unterstützen.
-
Eigentlich scheint mir die 'Lösung' zu einfach, aber ich poste
trotzdem mal, was ich mir gedacht habe:template<class T> struct IVisitorWrapper { virtual void Visit(T& t); }; template<class TVisitor, class T> struct VisitorWrapper { VisitorWrapper(TVisitor* visitor) : visitor(visitor) { } virtual void Visit(T& t){ this->visitor->Visit(t); } TVisitor* visitor; }; // Basisklasse der zu besuchenden Klassen struct Base { virtual ~Base() = 0; }; // Zwischenklasse, um Base-Derivate nicht zu verändern template <class TBase> struct BaseAcceptor { virtual void Accept(IVisitorWrapper<TBase>& visitor) = 0; TBase* base; }; template <class TBase> struct Acceptor : BaseAcceptor<TBase> { virtual void Accept(IVisitorWrapper<TBase>& visitor) { // Statischen Typ für Überladung bekannt machen visitor.Visit(base); } };
-
Ich bin mir nicht ganz sicher was Du genau meinst, aber wenn es genau das ist
Nexus schrieb:
Eine Klasse speichert einen Container mit polymorphen Basisklassenzeigern. In Wirklichkeit stecken dahinter verschiedene abgeleitete Objekte.
Nun will ich in dieser Klasse eine Schnittstelle anbieten, um für alle Elemente etwas zu tun. Naheliegend scheint ein Iterator-Interface, doch dann muss der Benutzer jeweils downcasten und Typunterscheidungen durchführen. Daher scheint es mir eleganter, einen Visitor zu nehmen, der den dynamischen Typen des jeweiligen Elements automatisch erkennt und eine spezifische Aktion durchführt.
könntest Du vielleicht boost::fusion::map benutzen, vielleicht auch in Kombination mit boost::function.
Vielleicht so
struct base { ... }; struct derived1 : base { ... }; struct derived2 : base { ... }: ... void visit_derived1( derived1 *d ) { ... } void visit_derived2( derived2 *d ) { ... } typedef fusion::map< fusion::pair< derived1 , boost::function1< void , derived1* > , fusion::pair< derived2 , boost::function2< void , derived2* > > default_switch_type( fusion::make_pair< derived1 >( visit_derived1 ) , fusion::make_pair< derived2 >( visit_derived2 ) ); template< class SwitchType = default_switch_type > struct visitor { SwitchType m_switch; visitor( const SwitchType &switch_ = SwitchType() ) : m_switch( switch_ ) template< class ToVisit > void operator()( ToVisit *t ) { visit( t , fusion::has_key< ToVisit )( m_switch ) ); } // visit this function if Key exist template< class ToVisit > void visit( ToVisit *t , boost::true_type ) { fusion::at_key< ToVisit >( m_switch )( t ); } template< class ToVisit > void visit( ToVisit *t , boost::false_type ) { // default ... } };Das ganze ist dann "konfigurierbar", der User kann eine eigene Sequenz angeben, welche dann besucht wird.
Die Frage ist jetzt wie der Container aussieht. Wenn er statisch ist ( boost::fusion, std::tr1::tuple ) kannst Du einfach fusion::for_each benutzen und es wird richtig gecastet.
Falls der Container dynamisch ist, kannst Du in
visitor::operator()noch einen Meta-Loop über all (statischen) Keys inSwitchTypeeinbauen. In jedem dieser Aufrufe machst Du einen check ob Du casten kannst und wenn ja wird die Funktion aufgerufen:struct call_visitor { const base* m_b; call_visitor( const base *b ) : m_b( b ) { }; template< class Key , class Value > void operator()( fusion::pair< Key , Value > p ) const { typedef typename boost::is_base_of< base , Key >::type base_dispatch_type; cout << "operator " << typeid( Key ).name() << " " << base_dispatch_type::value << endl; dispatch( p , base_dispatch_type() ); } template< class Key , class Value > void dispatch( fusion::pair< Key , Value > p , boost::true_type ) const { Value f = p.second; f( dynamic_cast< const Key* >( m_b ) ); } template< class Key , class Value > void dispatch( fusion::pair< Key , Value > p , boost::false_type ) const { cout << "Not good types!" << endl; } }; void visit( base* b ) { fusion::for_each( default_switch , call_visitor( b ) ); }
-
XSpille schrieb:
Eigentlich scheint mir die 'Lösung' zu einfach, aber ich poste
trotzdem mal, was ich mir gedacht habe:Naja, das Problem ist jetzt, dass hier
visitor.Visit(base);basenur ein Basisklassenzeiger ist, wodurch man keine Funktionsüberladung in der Visitor-Klasse nutzen kann.headmyshoulder schrieb:
Ich bin mir nicht ganz sicher was Du genau meinst, aber wenn es genau das ist könntest Du vielleicht boost::fusion::map benutzen, vielleicht auch in Kombination mit boost::function.
Vielen Dank für den Hinweis und den Code! Leider kann ich in der Bibliothek kein Boost benutzen, aber dein Vorschlag hat mich dazu gebracht, die Boost.Fusion-Bibliothek mal anzuschauen. Scheint wirklich noch ein paar interessante Konzepte zu haben...
Doch gleich eine Frage zu deinem Code: Müssen die einzelnen abgeleiteten Typen in der Sequenz nicht bekannt sein? Boost.Fusion basiert ja stark auf auf statischer Polymorphie (vom dynamischen Teil der Fusion habe ich bisher noch nicht viel entdeckt). Und ehlich gesagt schreckt mich die boost-typische Templateschlacht ein bisschen ab...

-
Nexus schrieb:
Vielen Dank für den Hinweis und den Code! Leider kann ich in der Bibliothek kein Boost benutzen, aber dein Vorschlag hat mich dazu gebracht, die Boost.Fusion-Bibliothek mal anzuschauen. Scheint wirklich noch ein paar interessante Konzepte zu haben...
Ok, dass per Hand zu entwickeln ist recht aufwendig. Sollte aber möglich sein, falls es nur um diese Funktionalität geht. Man könnte auch
std::tr1::tupledazu aufbohren und eine dazugehörigesfor_eachimplementieren.Nexus schrieb:
Doch gleich eine Frage zu deinem Code: Müssen die einzelnen abgeleiteten Typen in der Sequenz nicht bekannt sein? Boost.Fusion basiert ja stark auf auf statischer Polymorphie (vom dynamischen Teil der Fusion habe ich bisher noch nicht viel entdeckt). Und ehlich gesagt schreckt mich die boost-typische Templateschlacht ein bisschen ab...

Ja, müssen sie. Allerdings bekommt die Klasse Visitor die Sequenz als Template-Parameter mit übergeben. Der User kann also die Sequenz, und damit auch das Verhalten und Weiterleiten, selbst festlegen. Man könnte auch eine Default-Sequenz festlegen.
Ein weitere Möglichkeit wäre es, die Sequenz in einem Member zu verstecken, ähnlich wie Boost.Any. Dann kann die Sequenz auch dynamisch gesetzt und verändert werden.
-
headmyshoulder schrieb:
Ja, müssen sie. Allerdings bekommt die Klasse Visitor die Sequenz als Template-Parameter mit übergeben.
Auf Visitor-Seite ist es sogar erwünscht, dass die zu besuchenden Klassen als statische Typinformationen vorliegen. Auf der Seite der Klasse mit Container aus polymorphen Zeigern hingegen haben wir die Typen nicht (oder nur durch Type Erasure abstrahiert). Ich sehe daher eine grundsätzliche Schwierigkeit... Oder ich hab einfach noch nicht ganz verstanden, wie dynamisch Boost.Fusion sein kann.
boost::anywäre hingegen auch nur eine Wegkapselung des dynamischen Typen mittels Type Erasure, dann komm ich wieder nicht an die statischen Typen.
-
Nexus schrieb:
headmyshoulder schrieb:
Ja, müssen sie. Allerdings bekommt die Klasse Visitor die Sequenz als Template-Parameter mit übergeben.
Auf Visitor-Seite ist es sogar erwünscht, dass die zu besuchenden Klassen als statische Typinformationen vorliegen. Auf der Seite der Klasse mit Container aus polymorphen Zeigern hingegen haben wir die Typen nicht (oder nur durch Type Erasure abstrahiert). Ich sehe daher eine grundsätzliche Schwierigkeit... Oder ich hab einfach noch nicht ganz verstanden, wie dynamisch Boost.Fusion sein kann.
Wenn dynamisch für Dich bedeutet zur Laufzeit mit Code aus beliebigen Dlls/SO geht das natürlich nicht. Wenn dynamisch bedeutet dass der User das Verhalten festlegen kann schon.
In dem Beispiel bildet Fusion.Map einen Typ auf einen Funktor ab.
// Achtung kein C++ derived1 -> visit_derived1 derived2 -> visit_derived2 ...Diese Map kann der User selbst festlegen und verändern, aber nur während des Kompilierens. Wird jetzt der Container von
base*durchlaufen und der Visitor aufgerufen wird anhand dieser Abbildung der entsprechende Funktor mit dem gerade zu besuchenden Element aufgerufen. Ich glaub der folgende Code veranschaulicht das Recht gut:struct call_visitor { const base* m_b; call_visitor( const base *b ) : m_b( b ) { }; template< class Key , class Value > void operator()( fusion::pair< Key , Value > p ) const { const Key* k = dynamic_cast< const Key * >( m_b ); if( k != 0 ) { Value f = p.second; f( k ); } } }; void visit( base* b ) { fusion::for_each( switch , call_visitor( b ) ); }switchist hier die Fusion Map aus dem obigen Beispiel.Würde das Dein Problem lösen?
-
Mein Dynamic-Dispatcher ist inzwischen lauffähig, ich muss hauptsächlich noch ein paar Feineinstellungen vornehmen. Wenn ich das richtig sehe, habe ich sogar was Ähnliches wie dein Code nachgebaut, nur halt mehr auf die Laufzeit konzentriert.
// Klasse mit Container von polymorphen Zeigern struct User { void VisitEach(DynamicDispatcher<Base>& dispatcher) { for (/* Iterator itr über b */) dispatcher.Call(**itr); } std::vector<Base*> b; }; // Funktionen für den Besuch void Visit(Derived1&); void Visit(Derived2&); // Anwendung int main() { DynamicDispatcher<Base> dispatcher; dispatcher.Register<Derived1>(&Visit); dispatcher.Register<Derived2>(&Visit); User u; // fülle u.b mit Objekten u.VisitEach(dispatcher); }Ist nun halt kein Funktor mit überladenen
operator()-Funktionen mehr, aber das scheint eh nicht zu gehen. Zumindest nicht ohne Template-Gehacke und Ableiten von etlichen Klassen. Oder kann man mit Boost.Fusion das Ganze so hinkriegen, dass ein Visitor des Benutzers so aussieht (von mir aus kann er von einer Basisklasse erben)?struct Visitor { void operator() (Derived1&); void operator() (Derived2&); ... };Ansonsten bin ich mit meiner Methode recht zufrieden. Man muss die abgeleiteten Klassen nicht verändern und kommt als Benutzer fast ohne Templates aus. Was noch schön wäre, wenn man die Registrierung irgendwie automatisieren könnte, aber sonst kann ich damit leben.
-
Ein möglicher Ansatz zur Automatisierung:
// ---------------------------------------------------------------- // Bibliothek (virtuelle Destruktoren weggelassen) struct BaseVisitor { ... }; template <class D> struct Visitor { Visitor(); // Registriert Visit() dieser Klasse virtual void Visit(D&) = 0; // muss überschrieben werden }; // ---------------------------------------------------------------- // Anwendung struct MyVisitor : BaseVisitor, Visitor<Derived1>, Visitor<Derived2> { virtual void Visit(Derived1&); virtual void Visit(Derived2&); };Der Rest bliebe ungefähr gleich.
Fändet ihr das benutzerfreundlicher? Ich sehe eigentlich nicht viele Vorteile, abgesehen davon, dass man
DynamicDispatchernicht direkt anfassen muss.- Nur Methoden einer einzelnen Klasse zu verwenden ist auch mit
DynamicDispatchermöglich. - Man dupliziert auch hier Informationen: Abgeleitete Typen kommen als Template-Argumente von
Visitorsowie als Funktionsparameter vonVisit()vor. - Der Ansatz hier hat recht viel Overhead, da neben den VTables wahrscheinlich auch ein
dynamic_castpro Visitor nötig ist. Zusätzlich zum Dispatch-Mechanismus, der ja auch nicht ganz gratis ist. - Man ist grundsätzlich weniger flexibel, weil bereits bestehende Funktionen nicht zum Besuch verwendet werden können. Man muss eine Überladung in
MyVisitorschreiben. Hingegen kannDynamicDispatchmit freien Funktionen, Memberfunktionen und Funktoren umgehen.
Aber seid ehrlich, was würdet ihr als Benutzer lieber verwenden?
- Nur Methoden einer einzelnen Klasse zu verwenden ist auch mit
-
Nexus schrieb:
Mein Dynamic-Dispatcher ist inzwischen lauffähig, ich muss hauptsächlich noch ein paar Feineinstellungen vornehmen. Wenn ich das richtig sehe, habe ich sogar was Ähnliches wie dein Code nachgebaut, nur halt mehr auf die Laufzeit konzentriert.
Jepp, ich denke dass das unter der Haube recht ähnlich zu meinem Ansatz ist. Kannst Du den Code von Deinem Dispatcher posten, insbesondere von Call und Register?
Nexus schrieb:
Ist nun halt kein Funktor mit überladenen
operator()-Funktionen mehr, aber das scheint eh nicht zu gehen. Zumindest nicht ohne Template-Gehacke und Ableiten von etlichen Klassen. Oder kann man mit Boost.Fusion das Ganze so hinkriegen, dass ein Visitor des Benutzers so aussieht (von mir aus kann er von einer Basisklasse erben)?Ich glaub prinzipiell kannst Du in
fusion::mapjeden beliebigen Typ reinpacken. Funktoren hab ich nur gewählt weil die ziemlich allgemein sind.
-
Nexus schrieb:
Ein möglicher Ansatz zur Automatisierung:
// ---------------------------------------------------------------- // Bibliothek (virtuelle Destruktoren weggelassen) struct BaseVisitor { ... }; template <class D> struct Visitor { Visitor(); // Registriert Visit() dieser Klasse virtual void Visit(D&) = 0; // muss überschrieben werden }; // ---------------------------------------------------------------- // Anwendung struct MyVisitor : BaseVisitor, Visitor<Derived1>, Visitor<Derived2> { virtual void Visit(Derived1&); virtual void Visit(Derived2&); };Der Rest bliebe ungefähr gleich.
Fändet ihr das benutzerfreundlicher?
Vielleicht müssen sich ja beiden Methoden gar nicht ausschliessen. Wenn Du das hinbekommst kann der User entscheiden was er lieber benutzt, entweder einen grossen Visitor oder viele kleine. Ich persönlich finde den ersten Ansatz ein bisschen besser. Die Mehrfachvererbung ist mir ein kleines bisschen suspekt.
-
headmyshoulder schrieb:
Kannst Du den Code von Deinem Dispatcher posten, insbesondere von Call und Register.
Was? Der ist höchst geheim!

Nein, kann ich gerne machen. Aber wie schon angetönt muss das Ganze noch verbessert werden, richtig getestet habe ich auch noch nicht. Ich würds also noch nicht direkt verwenden. Wenn du aber interessiert bist, kann ich dir eine E-Mail schreiben, wenn ich fertig bin (das wird wahrscheinlich Teil einer grösseren Bibliothek).
Hier mal das Klassentemplate
DynamicDispatchersoweit (Memberfunktionen der Übersicht halber inline). Jetzt fällt mir gerade auf, dass der Code in denRegister()-Überladungen in eine private Funktion ausgelagert werden könnte. Undstd::mapwerde ich wohl durch einen sortiertenstd::vectorersetzen. Es gibt noch einiges zu tun... Möglicherweise werde ich später auch einen Dispatcher für Funktionen mit zwei Parametern implementieren.
// B = Basisklasse der Hierarchie // R = Rückgabetyp der dispatchten Funktionen template <class B, typename R = void> class DynamicDispatcher : private NonCopyable { private: // Map, die type-ids auf Funktionen abbildet (TypeInfo ist ein Wrapper um std::type_info) typedef std::map<detail::TypeInfo, detail::FunctionBase<B, R>*> FnMap; public: // Destruktor ~DynamicDispatcher() { FOREACH(typename FnMap, myMap, itr) // Makro für STL-For-Schleife delete itr->second; } // Registrierung von globalen Funktionen // D = Abgeleitete konkrete Klasse template <class D> void Register(R (*fn)(D&)) { typename FnMap::const_iterator itr = myMap.find(typeid(D)); if (itr != myMap.end()) throw InvalidFunctionRegistration(); myMap[typeid(D)] = new detail::GlobalFunction<B, D, R>(fn); } // Registrierung von Memberfunktionen mit this-Zeiger // D = Abgeleitete konkrete Klasse // C = Klasse mit aufzurufender Memberfunktion template <class D, class C> void Register(R (C::*fn)(D&), C* thisPtr) { typename FnMap::const_iterator itr = myMap.find(typeid(D)); if (itr != myMap.end()) throw InvalidFunctionRegistration(); myMap[typeid(D)] = new detail::MemberFunction<B, D, R, C>(fn, thisPtr); } // Registrierung beliebiger Funktionsobjekte // D = Abgeleitete konkrete Klasse // Fn = Typ des Funktors template <class D, typename Fn> void Register(const Fn& fn) { typename FnMap::const_iterator itr = myMap.find(typeid(D)); if (itr != myMap.end()) throw InvalidFunctionRegistration(); myMap[typeid(D)] = new detail::GlobalFunction<B, D, R, Fn>(fn); } // Aufruf R Call(B& arg) const { typename FnMap::const_iterator itr = myMap.find(typeid(arg)); if (itr == myMap.end()) throw InvalidFunctionCall(); return itr->second->Call(arg); } private: FnMap myMap; };Und hier noch die Implementierung innerhalb des Namensraums
detail. Ich hätte stattdessenstd::tr1::functionnehmen können, aber wegen Problemen bei Derived-Base-Konvertierungen der Argumente habe ich das selbst gebaut (ist ja nicht viel Code):template <class B, typename R> struct FunctionBase { virtual R Call(B& ref) = 0; virtual ~FunctionBase() {} }; template <class B, class D, typename R, typename Fn = R(*)(D&)> struct GlobalFunction : FunctionBase<B, R> { explicit GlobalFunction(const Fn& fn) : fn(fn) { } virtual R Call(B& ref) { return fn( static_cast<D&>(ref) ); } Fn fn; }; template <class B, class D, typename R, class C> struct MemberFunction : FunctionBase<B, R> { typedef R (C::*MemFn)(D&); MemberFunction(MemFn fn, C* thisPtr) : fn(fn) , thisPtr(thisPtr) { } virtual R Call(B& ref) { return (thisPtr->*fn)( static_cast<D&>(ref) ); } MemFn fn; C* thisPtr; };Vielen Dank nochmals für deine Hilfe! Ich hoffe, du bist nicht enttäuscht, dass ich nun doch nicht Boost.Fusion verwende. Aber ich werds mir für später merken...

-
headmyshoulder schrieb:
Vielleicht müssen sich ja beiden Methoden gar nicht ausschliessen. Wenn Du das hinbekommst kann der User entscheiden was er lieber benutzt, entweder einen grossen Visitor oder viele kleine.
Hm... Ich halte die Schnittstelle generell lieber klein. Hier ist es vielleicht besser, wenn es nur einen Weg gibt.
headmyshoulder schrieb:
Die Mehrfachvererbung ist mir ein kleines bisschen suspekt.
Ja, das Prinzip habe ich wie so vieles von Alexandrescu. Ist ein bisschen speziell, das stimmt... Aber wären dir Typlisten lieber?

-
Nexus schrieb:
Nein, kann ich gerne machen. Aber wie schon angetönt muss das Ganze noch verbessert werden, richtig getestet habe ich auch noch nicht. Ich würds also noch nicht direkt verwenden. Wenn du aber interessiert bist, kann ich dir eine E-Mail schreiben, wenn ich fertig bin (das wird wahrscheinlich Teil einer grösseren Bibliothek).
Ja, gerne.
Dein Ansatz ist ein bisschen anders, sollte aber die gleiche Funktionalität haben. Ich glaube die Call-Funktion muss auch noch templatisiert werden, oder? Was interessant ist, dass bei beiden Ansätzen ein Typ erstmal auf den Parent-Type runtergecastet wird und danach wieder hoch. Das runtercasten passiert bei beiden Ansätzen an unterschiedlichen Stellen, bei Dir nach der Suche für den spezifischen Visitor und bei mir davor.
Nexus schrieb:
headmyshoulder schrieb:
Vielleicht müssen sich ja beiden Methoden gar nicht ausschliessen. Wenn Du das hinbekommst kann der User entscheiden was er lieber benutzt, entweder einen grossen Visitor oder viele kleine.
Hm... Ich halte die Schnittstelle generell lieber klein. Hier ist es vielleicht besser, wenn es nur einen Weg gibt.
Das was ich meinte kannst Du schon machen.0 Zum Beispiel einen grossen Visitor benutzen, und dessen Methoden registrieren; oder viele kleine Vistor benutzen, welche separat regisitriert werden.
-
headmyshoulder schrieb:
Ich glaube die Call-Funktion muss auch noch templatisiert werden, oder?
Ich denke nicht, denn beim Aufruf ist ja nur eine Basisklassenreferenz (also
B&) vorhanden. Der dynamische Typ kann nicht durch ein Template erkannt werden.headmyshoulder schrieb:
Das was ich meinte kannst Du schon machen.0 Zum Beispiel einen grossen Visitor benutzen, und dessen Methoden registrieren; oder viele kleine Vistor benutzen, welche separat regisitriert werden.
Das wäre dann eher auf Benutzerseite, oder? Das habe ich auch mit "Nur Methoden einer einzelnen Klasse zu verwenden ist auch mit DynamicDispatcher möglich" gemeint. Also sowas:
struct MyVisitor { void operator() (Derived1&); void operator() (Derived2&); }; int main() { MyVisitor v; DynamicDispatcher dispatcher; dispatcher.Register<Derived1>(&MyVisitor::operator(), &v); dispatcher.Register<Derived2>(&MyVisitor::operator(), &v); ... }Ein Problem gibt es allerdings. Da ich
typeidverwende, muss der Typ exakt stimmen. Derived-to-Base-Konvertierungen sind beim Aufruf also nicht möglich. Ich weiss nicht, ob es dafür überhaupt eine akzeptable Lösung gibt. Alexandrescu hat das Problem in seinem Buch leider nicht gelöst...struct Derived2a : Derived2 {}; int main() { // dispatcher wie oben ... Derived2a obj; dispatcher.Call(obj); // Ruft nicht die Derived2-Version auf } // da typeid(Derived2a) != typeid(Derived2)
-
Wenn du einen hübschen Funktor mit einem oder mehreren ()-Operatoren haben willst, muss m.E. dieser irgendwie zugänglich machen, für welche Typen er konzipiert ist (also den ()-Operator überladen hat). Anders ist eine statische Wahl der richtigen Funktion zur Compilezeit (aus den überladenen Funktionen) ja nicht möglich.
Meine Idee sähe so aus:
template<class Derived, class R, class SupportedTypes> // SupportedTypes ist MPL-Sequenz aller unterstützten Typen des Visitors struct visitor_base { BOOST_MPL_ASSERT((boost::mpl::is_sequence<SupportedTypes>)); typedef R result_type; template<class T> result_type apply(T* ptr) { return apply_impl<SupportedTypes>(static_cast<Derived&>(*this), ptr); } private: template<class Types, class T> result_type apply_impl(Derived& d, T* ptr, boost::mpl::false_type = boost::mpl::false_type()) { typedef boost::mpl::front<Types>::type current_type; typedef boost::mpl::pop_front<Types>::type next; if(typeid(*ptr) == typeid(current_type)) // Juhuu, Visitor unterstützt Typ return d(boost::polymorphic_downcast<current_type&>(*ptr)); // Also ()-Operator aufrufen else return apply_impl<next>(d, ptr, boost::mpl::empty<next>); } template<class Types, class T> result_type apply_impl(Derived& d, T* ptr, boost::mpl::true_type) { // der Visitor unterstützt den Typ von *ptr nicht. Wahlweise assert, ignore o.ä. return result_type(); } };Genutzt werden kann das dann so:
struct my_visitor : visitor_base<my_visitor, int, boost::mpl::vector<Derived1, Derived2, Derived2a> > { int operator()(Derived1&) const; int operator()(Derived2&) const; }; struct container { std::vector<base*> objects; template<class Visitor> void apply_visitor(const Visitor& v) { FOREACH(base* b, objects) v.apply(b); // vergessen wir den Rückgabewert... } }; // ... container c; c.apply_visitor(my_visitor());(alles ungetestet. Solltest du kein boost verwenden können, kannst du das wenige mpl-Zeugs sicher auch selbst schreiben).
Das hat immernoch das Problem mit dem Derived2a vs. Derived, man muss Derived2a in der Liste der unterstützden Typen angeben. Der Derived&-()-Operator wird dann trotzdem aufgerufen. Das sollte aber fast nicht anders gehen, es sei denn man könnte zur Laufzeit ermitteln, ob ein Objekt einen dynamnischen Typ mit einem speziellen Basistyp hat.
Und man muss die unterstützen Typen mehrfach angeben, wie schon eingangs erläutert (ich denke es ist nicht möglich, die Typen mit Metaprogrammierung aus den in der Klasse überladenen ()-Operatoren zu ermitteln - was ist, wenn man ein Template nutzt?)Eventuell ist dein Ansatz über Dynamic Dispatch aber auch günstiger.
-
Nexus schrieb:
headmyshoulder schrieb:
Ich glaube die Call-Funktion muss auch noch templatisiert werden, oder?
Ich denke nicht, denn beim Aufruf ist ja nur eine Basisklassenreferenz (also
B&) vorhanden. Der dynamische Typ kann nicht durch ein Template erkannt werden.Aber dann ist die typeid in
R Call(B& arg) const { typename FnMap::const_iterator itr = myMap.find(typeid(arg)); ... }immer gleich, nämlich die typeid von B. Oder übersehe ich was?
Nexus schrieb:
Ein Problem gibt es allerdings. Da ich
typeidverwende, muss der Typ exakt stimmen. Derived-to-Base-Konvertierungen sind beim Aufruf also nicht möglich.Hier könnte vielleicht sowas wie
boost::is_base_of<>helfen aus den TypeTraits helfen, ich bin mir aber nicht sicher.
-
ipsec schrieb:
Wenn du einen hübschen Funktor mit einem oder mehreren ()-Operatoren haben willst, muss m.E. dieser irgendwie zugänglich machen, für welche Typen er konzipiert ist (also den ()-Operator überladen hat). Anders ist eine statische Wahl der richtigen Funktion zur Compilezeit (aus den überladenen Funktionen) ja nicht möglich.
Meine Idee sähe so aus:
Deine Idee find ich auch ganz nett, allerdings hast Du immer das Problem, dass Du
apply()immer mit dem "richtigen" Typ aufrufen musst. Inipsec schrieb:
struct container
{
std::vector<base*> objects;template<class Visitor>
void apply_visitor(const Visitor& v)
{
FOREACH(base* b, objects)
v.apply(b); // vergessen wir den Rückgabewert...
}
};ist
bbeispielsweise immer vom Typbase*, obwohl man hier eigentlich den "richtigen" Type haben will. Das passiert in meinem Beispiel nicht, da dort probiert wird, den Typ runterzucasten, und wenn das klappt wird der Visitor mit dem runtergecastet Typ besucht.