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.


  • Mod

    Oder gibt es da eine elegantere Lösung?

    Was spricht dagegen std::function zu verwenden und als Konstruktorargument statt dem eigentlichen Funktor einen std::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.


  • Mod

    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 zweimal public 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 einen std::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! 👍


  • Mod

    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 einfach

    std::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.


  • Mod

    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...


Log in to reply