virtuelles Methoden-Template
-
Ich hätte gerne eine abstrakte Basisklasse mit einer virtuellen Methode:
template <class Random> virtual void foo(Random& rnd) = 0;
Dabei soll das Template Random für ein Objekt stehen, dass über operator() Zufallszahlen erzeugt. Das kompiliert jedoch nicht.
Mein erster Ansatz war sowas hier:
virtual void foo(std::function<size_t()> rnd) = 0;
Allerdings wird beim Aufruf
std::mt19937 rnd; object.foo(rnd);
eine Kopie von rnd erzeugt, was bei Zufallsgeneratoren natürlich ziemlich unschön ist, wenn man denselben Generator erneut verwenden möchte.
Wie könnte man das Problem umgehen?
Dasselbe Problem habe ich bei
template <class Point> virtual void bar(Point& p) = 0;
wobei p den operator[] überlädt. Als Point könnte man somit z.B. int* oder std::vector<int> einsetzen. Gibt es hier die Möglichkeit selbst eine Art Wrapper wie std::function zu schreiben? Und wenn ja wie? Oder gibt es vielleicht schon eine Lösung in der STL oder ggf. auch in Boost?
-
virtuelle Methoden können kein Template sein, das nervt mich auch von Zeit zu Zeit.
Das, was std::function macht ist Type-Erasure.
Für deinen Punkt geht das auch, grob so:
template <typename Ret, typename Arg>class indexable { public: template <typename T> indexable(T &t) : base_(new basic_indexable<T>(t)) {} Ret operator[](const Arg &arg) { return base_->operator[](arg); } private: class base_indexable { public: virtual Ret operator[](const Arg &arg) = 0; }; template <typename T> class basic_indexable : public base_indexable { public: basic_indexable(T &t) noexcept : t_(&t) {} Ret operator[](const Arg &arg) override { return (*t_)[arg]; } private: T *t_ }; base_indexable *base_: };
(Destruktoren fehlen etc.)
-
Und für den ersten Fall (Random per Referenz übergeben) würde ich dann auch sowas schreiben? Oder gibt es da eine elegantere Lösung?
-
Ramanujan schrieb:
Und für den ersten Fall (Random per Referenz übergeben) würde ich dann auch sowas schreiben? Oder gibt es da eine elegantere Lösung?
Wenn du keine Kopie haben willst, musst du das - AFAIK - selber so machen.
Evtl. kann man da jedoch noch tricksen um das new zu vermeiden.
Warte mal, mir fällt was ein:class base_function {...}; template <typename Func> class basic_function : ... {...}; // hier dann einen Pointer auf das Funktionsobjekt speichern template <...> // Funktionssignatur, die gespeichert wird - oder hardcode fuer das Randomding class generic_function { public: generic_function(base_function *fnc) noexcept : fnc_(fnc) {} // operator() }; class foo { public: template <typename Fnc> void do_stuff(Fnc &&fnc) { basic_function<Fnc> basic(forward<Fnc>(fnc)); impl_do_stuff(&basic); } protected: virtual void impl_do_stuff(generic_function fnc) { ... } };
So haste zumindest kein new mehr.
-
Oder gibt es da eine elegantere Lösung?
Was spricht dagegen
std::function
zu verwenden und als Konstruktorargument statt dem eigentlichen Funktor einenstd::reference_wrapper
um den Funktor drum? Damit wären wenigstens die Zeiger raus, die ja bekanntlich in modernem C++ nie in nicht gekapselter Form auftreten.
-
template< typename T > struct ref_function : std::function<T> { template< typename F > ref_function( F& f ) : std::function<T>{ std::ref(f) } {} template< typename ... Args > ref_function( Args&&... args ) : std::function<T>{ std::forward<Args>(args)... } {} }; ....... virtual void foo( ref_function<std::size_t()> f ) = 0;
Sollte ausreichen.
(@Nexus: Ich habe
struct
missbraucht, aber ich war zu faul um zweimalpublic
zu schreiben.)
-
Arcoth schrieb:
Oder gibt es da eine elegantere Lösung?
Was spricht dagegen
std::function
zu verwenden und als Konstruktorargument statt dem eigentlichen Funktor einenstd::reference_wrapper
um den Funktor drum? Damit wären wenigstens die Zeiger raus, die ja bekanntlich in modernem C++ nie in nicht gekapselter Form auftreten.Falls der Sarkasmus gegen mich war: Das ist ein Fall von Kapselung. Und ich bezog mich auf besitzende Pointer.
Ansonsten guter Ansatz!
-
Falls der Sarkasmus gegen mich war
Nein, auf keinen Fall! Der war gegen SeppJ.
-
@Arcoth
std::reference_wrapper ist gut. Aber wieso so kompliziert?
Wieso nicht einfachstd::mt19937 rnd; object.foo(std::ref(rnd));
?
Oder ist euch das zu "unsicher" - weil man beim Aufruf natürlich auch vergessen kann das
std::ref()
zu schreiben, und dann nach wie vor eine Kopie des RNG gemacht wird.
Das wäre natürlich ein Argument.
-
Oder ist euch das zu "unsicher" - weil man beim Aufruf natürlich auch vergessen kann das std::ref() zu schreiben, und dann nach wie vor eine Kopie des RNG gemacht wird.
Genau das.
Edit: Übrigens vielen Dank - ich hatte komischerweise völlig vergessen, dass es
std::ref
gibt! Habe das obige Beispiel entsprechend angepasst.
-
Das mit ref_function ist nice und mit std::ref ebenso, aber mir fiel gerade aus, dass ich auf rnd.max() zugreife. Da hat sich das wrappen mit std::function direkt wieder erledigt. Ich denke, dass ich dann wohl einen eigenen Wrapper für Zufallsgeneratoren schreiben müsste.
Fazit:
Templates sind eine schöne Sache. Und Vererbung ebenso. Aber beides zusammen ist furchtbar
-
Ramanujan schrieb:
Templates sind eine schöne Sache. Und Vererbung ebenso. Aber beides zusammen ist furchtbar
Im Gegenteil, mit Type Erasure hast du wahnsinnige Abstraktionsmöglichkeiten.
std::function
ist ein gutes Beispiel dafür...