Pointer innerhalb Klasse auf Memberfunktion


  • Administrator

    @FrEEzE2046,
    Am Anfang des PDFs wird aber erklärt, wie man es bisher in den C++ Bibliotheken gemacht hat. Und genau so macht es auch Boost.Function, Boost.Bind usw.
    Zudem wird auf Boost.Preprocessor verwiesen, was man zur Hilfe heranziehen kann, was bei Boost für das Simulieren von variadic templates auch gemacht wird.

    Grundsätzlich steht alles da am Anfang von Kapitel 2. Es ist sehr kurz gehalten, aber fasst es eigentlich vollständig zusammen.

    Grüssli


  • Administrator

    Sorry für den Doppelpost, aber da schon einige Zeit vergangen ist, wollte ich nicht nur ein Edit machen. Hier noch ein kurzes dahingerotztes Beispiel 🙂

    #include <iostream>
    
    #include <boost/preprocessor.hpp>
    
    #define MY_MACRO_TEMPLATE_TYPES(z, n, data) , typename T##n
    #define MY_MACRO_TEMPLATE_ARGS(z, n, data) , T##n const& arg##n
    #define MY_MACRO_TEMPLATE_OUTPUT(z, n, data) << arg##n
    
    #define MY_MACRO_PRINT_FUNCTION(z, n, data) \
    template<typename T0 BOOST_PP_REPEAT_FROM_TO(1, n, MY_MACRO_TEMPLATE_TYPES, data)>\
    void print(T0 const& arg0 BOOST_PP_REPEAT_FROM_TO(1, n, MY_MACRO_TEMPLATE_ARGS, data)) \
    { \
        std::cout BOOST_PP_REPEAT(n, MY_MACRO_TEMPLATE_OUTPUT, data) ;\
    } \
    
    #define MY_MACRO_CREATE_PRINT_FUNCTIONS(n) \
        BOOST_PP_REPEAT_FROM_TO(1, BOOST_PP_INC(n), MY_MACRO_PRINT_FUNCTION, _)
    
    MY_MACRO_CREATE_PRINT_FUNCTIONS(10)
    
    int main()
    {
        print("Hello", " World", '!', '\n', "Test: ", 443, '\n');
    
        return 0;
    }
    

    Die maximale Anzahl an Parametern ist 10. Also quasi Variadic Templates beschränkt auf 10 Parametern. Das ganze präsentiert an einer print Funktion. Sieht hässlich aus, nicht? 😃
    Variadic Templates aus C++0x werden diese Hässlichkeiten hoffentlich entfernen können 😉

    Grüssli



  • Ich steig einfach nicht dahinter, wie man die Template-Argumente zunächst auf "typename Signature" begrenzt und dann intern in die Spezialisierungen aufsplittet.

    Mir ist klar, dass das mit irgendwelchen Makro-Verschachtelungen geschieht, aber ich versteh noch nicht wie. Würde mich aber sehr interessieren.

    Das einzige was ich in der Richtung hinbekomme ist sowas hier:

    #define PP_REPEAT_1( param )	param ## 0
    #define PP_REPEAT_2( param )	param ## 0, param ## 1
    #define PP_REPEAT_3( param )	param ## 0, param ## 1, param ## 2
    
    #define ENUM_PARAMS( count, param )			PP_REPEAT_##count( param )
    
    template<ENUM_PARAMS( 3, typename T )> class function {};
    

    Das ändert aber nichts daran, dass ich

    function<int, int, int> f;
    

    Benutzen muss. Wie kann das anders gelöst werden?


  • Administrator

    Hier mal ein wenig Beispielcode (Achtung, sehr viele wichtige Dinge wie Kopierkonstruktoren usw. usf. wurden weggelassen):

    #include <memory>
    
    template<typename T>
    struct FunctorImpl;
    
    template<typename R>
    struct FunctorImpl<R()>
    {
        typedef R ResultType;
    
        virtual R call() const;
        virtual FunctorImpl* clone() const;
    };
    
    template<typename R, typename P0>
    struct FunctorImpl<R(P0)>
    {
        typedef R ResultType;
        typedef P0 Parameter0;
    
        virtual R call(Parameter0) const;
        virtual FunctorImpl* clone() const;
    };
    
    template<typename R, typename P0, typename P1>
    struct FunctorImpl<R(P0, P1)>
    {
        typedef R ResultType;
        typedef P0 Parameter0;
        typedef P1 Parameter1;
    
        virtual R call(Parameter0, Parameter1) const = 0;
        virtual FunctorImpl* clone() const = 0;
    };
    
    // usw. halt mit Makros aktuell
    
    template<typename ImplT, typename FuncT>
    struct FunctorHandler
        : ImplT
    {
    private:
        FuncT m_func;
    
    public:
        FunctorHandler(FuncT func)
            : m_func(func)
        {
        }
    
    public:
        typename ImplT::ResultType call() const
        {
            return m_func();
        }
    
        typename ImplT::ResultType call(typename ImplT::Parameter0 p0) const
        {
            return m_func(p0);
        }
    
        typename ImplT::ResultType call(typename ImplT::Parameter0 p0, typename ImplT::Parameter1 p1) const
        {
            return m_func(p0, p1);
        }
    
        // usw. mit Makros
    
        FunctorHandler* clone() const
        {
            return new FunctorHandler(*this);
        }
    };
    
    template<typename T>
    struct Functor
    {
    private:
        typedef FunctorImpl<T> Impl;
        std::auto_ptr<Impl> m_impl;
    
    public:
        template<typename FuncT>
        Functor(FuncT func)
            : m_impl(new FunctorHandler<Impl, FuncT>(func))
        {
        }
    
    public:
        typename Impl::ResultType operator ()() const
        {
            return m_impl->call();
        }
    
        typename Impl::ResultType operator ()(typename Impl::Parameter0 p0) const
        {
            return m_impl->call(p0);
        }
    
        typename Impl::ResultType operator ()(typename Impl::Parameter0 p0, typename Impl::Parameter1 p1) const
        {
            return m_impl->call(p0, p1);
        }
    
        // usw. mit Makros
    };
    
    // ...
    
    #include <iostream>
    
    int foo(int x, double y)
    {
        return static_cast<int>(x * y);
    }
    
    int main()
    {
        Functor<int(int, double)> functor(&foo);
        return functor(4, 22.3);
    }
    

    Es gibt auch noch andere Vorgehensweisen, habe das jetzt nur so mehr oder weniger schnell aus dem Kopf zusammengebaut. Ich hoffe allerdings, dass es das eigentliche Prinzip erklärt.

    Grüssli



  • FrEEzE2046 schrieb:

    Das ändert aber nichts daran, dass ich

    function<int, int, int> f;
    

    Benutzen muss. Wie kann das anders gelöst werden?

    Defaultparameter 🙂

    EDIT:
    Ja, camper hat natürlich recht. Weiss auch nicht wie ich darauf gekommen bin, dass man Klassentemplates überladen könnte.



  • LordJaxom schrieb:

    Kurz: Das liegt daran, dass Du ein Template mit drei Parametern erzeugst. Boost.Function hat aber mehrere Templates, eines mit 0, eines mit einem, eines mit zwei, ...., eines mit zehn Parametern. Die Templates heissen alle gleich, weshalb es für Dich so aussieht als wäre es variadic.

    Zudem gäbe es noch die Möglichkeit für alle Parameter Defaults anzugeben.

    Ja, dass ist mir schon klar. Entschuldige, ich hab mich wieder unpräzise ausgedrückt.

    Beispiel:

    struct nil;
    
    template<typename R = nil, typename T0 = nil, typename T1 = nil, typename T2 = nil> class function;
    
    template<> class function<> {};
    template<typename R> class function<R> {};
    template<typename R, typename T0> class function<R, T0> {};
    template<typename R, typename T0, typename T1> class function<R, T0, T1> {};
    
    // Trotzdem:
    function<int, int> f1;
    function<int, int, int> f2;
    

    Ich frage mich halt, wie es gemacht wird einen Funktionstyp anzugeben (z.B. void (int, double) und den dann intern dem Template:

    template<typename R = nil /*void*/, typename T0 = nil /*int*/, typename T1 = nil /*double*/> class function.
    

    zuzuordnen.


  • Mod

    Klassentemplates können nicht überladen werden.



  • FrEEzE2046 schrieb:

    Ich frage mich halt, wie es gemacht wird einen Funktionstyp anzugeben (z.B. void (int, double) und den dann intern dem Template:

    template<typename R = nil /*void*/, typename T0 = nil /*int*/, typename T1 = nil /*double*/> class function.
    

    zuzuordnen.

    Templatespezialisierungen, so wie Dravere es vor meinem Unsinnsposting gezeigt hat.



  • camper schrieb:

    Klassentemplates können nicht überladen werden.

    Hat wer überhaupt gesagt?
    Ich habe mal die syntaktischen Fehler aus meinem Posting beseitigt. Danke an alle, ich hab jetzt verstanden, wie`s gemacht wird 😉



  • FrEEzE2046 schrieb:

    camper schrieb:

    Klassentemplates können nicht überladen werden.

    Hat wer überhaupt gesagt?

    Ich 🙂



  • Hallo mal wieder,

    du hast so schön geschrieben: "usw. mit Makros ..."

    Wie genau gehts denn nun mit Makros weiter? ;-) So kann mans ja nicht implementieren, sonst bekommt man ja die schöne Meldung:

    'void Functor<T>::operator ()(int) const' : member function already defined or declared
    

    ... sobald man eine Funktion übergeben will, die weniger Parameter als (in deinem Beispiel:) 2 hat.

    Wie genau kann man das mit Makros lösen?


  • Administrator

    Oh, habe ich etwas vergessen gehabt. War wie gesagt aus dem Kopf heraus. Den Code ein wenig modifiziert:

    #include <memory> 
    
    struct EmptyType { };
    
    template
    <
    	typename P0 = EmptyType,
    	typename P1 = EmptyType,
    	typename P2 = EmptyType
    	// usw. bis maximale Anzahle Parameter
    >
    struct Parameters
    {
    	typedef P0 Type0;
    	typedef P1 Type1;
    	typedef P2 Type2;
    	// usw. bis maximale Anzahle Parameter
    
    	// Beide Listen kann man auch mit Makros bauen lassen,
    	// somit auch die ganze Klasse ...
    };
    
    template<typename T> 
    struct FunctorImpl; 
    
    template<typename R> 
    struct FunctorImpl<R()> 
    { 
    	typedef R ResultType;
    	typedef Parameters<> Parameters;
    
    	virtual R call() const = 0; 
    	virtual FunctorImpl* clone() const = 0; 
    }; 
    
    template<typename R, typename P0> 
    struct FunctorImpl<R(P0)> 
    { 
    	typedef R ResultType;
    	typedef Parameters<P0> Parameters;
    
    	virtual R call(P0) const = 0; 
    	virtual FunctorImpl* clone() const = 0; 
    }; 
    
    template<typename R, typename P0, typename P1> 
    struct FunctorImpl<R(P0, P1)> 
    { 
    	typedef R ResultType;
    	typedef Parameters<P0, P1> Parameters;
    
    	virtual R call(P0, P1) const = 0; 
    	virtual FunctorImpl* clone() const = 0; 
    }; 
    
    // usw. halt mit Makros aktuell 
    
    template<typename ImplT, typename FuncT> 
    struct FunctorHandler 
    	: ImplT 
    { 
    private: 
    	FuncT m_func; 
    
    public: 
    	FunctorHandler(FuncT func) 
    		: m_func(func) 
    	{ 
    	} 
    
    public: 
    	typename ImplT::ResultType call() const 
    	{ 
    		return m_func(); 
    	} 
    
    	typename ImplT::ResultType call(typename ImplT::Parameters::Type0 p0) const 
    	{ 
    		return m_func(p0); 
    	} 
    
    	typename ImplT::ResultType call(typename ImplT::Parameters::Type0 p0, typename ImplT::Parameters::Type1 p1) const 
    	{ 
    		return m_func(p0, p1); 
    	} 
    
    	// usw. mit Makros 
    
    	FunctorHandler* clone() const 
    	{ 
    		return new FunctorHandler(*this); 
    	} 
    }; 
    
    template<typename T> 
    struct Functor 
    { 
    private: 
    	typedef FunctorImpl<T> Impl; 
    	std::auto_ptr<Impl> m_impl; 
    
    public: 
    	template<typename FuncT> 
    	Functor(FuncT func) 
    		: m_impl(new FunctorHandler<Impl, FuncT>(func)) 
    	{ 
    	} 
    
    public: 
    	typename Impl::ResultType operator ()() const 
    	{ 
    		return m_impl->call(); 
    	} 
    
    	typename Impl::ResultType operator ()(typename Impl::Parameters::Type0 p0) const 
    	{ 
    		return m_impl->call(p0); 
    	} 
    
    	typename Impl::ResultType operator ()(typename Impl::Parameters::Type0 p0, typename Impl::Parameters::Type1 p1) const 
    	{ 
    		return m_impl->call(p0, p1); 
    	} 
    
    	// usw. mit Makros 
    }; 
    
    // ... 
    
    #include <iostream> 
    
    int foo() 
    { 
    	return 0; 
    } 
    
    int main() 
    { 
    	Functor<int()> functor(&foo); 
    	std::cout << functor() << std::endl;
    	return 0;
    }
    

    Jetzt geht es auch mit weniger. Hmmm, die Verwendung von Typlisten wäre hier ziemlich von Vorteil, aber bin jetzt zu Faul dieses Zeug auch noch hinzuschreiben ...
    Wesentlicher Unterschied zur vorherigen Version ist die Struktur Parameters . Problem war natürlich, dass nicht alle die maximale Parameter Zahl aufweisen, daher müssen diese mit dem EmptyType besetzt werden. Die Struktur Parameters dient zur Vereinfachung. Über eine Typliste hätte man das ganze wohl noch mehr vereinfachen können.

    Die Makros selber sind nur wichtig, wenn du mehr als 2 Parameter aktzeptieren möchtest. Wie das dann funktioniert, habe ich schon weiter vorne beschrieben mit der print Funktion. Wenn du wirklich beide Vorgehen vollständig verstanden hast, solltest du in der Lage sein, diese zu kombininieren.

    Aber ich glaube, ich empfehle hier langsam lieber das folgende Buch:
    Modern C++ Design von Andrei Alexandrescu
    Modern C++ Design | ISBN: 0201704315

    Dort kommen solche Themen dran und werden ausführlich erklärt und nicht nur auf die Schnelle aus dem Kopf heraus 😉

    Grüssli


Anmelden zum Antworten