Template für Memberfunktionspointer



  • Hi, ich bräuchte mal Ideen für folgende Problemstellung.
    Und zwar habe ich eine Klasse Test mit einer "Setter" MemberFunktion die drei Parameter hat.
    Die Funktionen können aber letztendlich beliebige Signaturen haben.
    Die drei Parameter sind dabei alles Typen die eine gemeinsame Basisklasse haben.
    Sowieso ist jedes Objekt von dieser Basisklasse abgeleitet.

    class MyBase
    {
    public:
    	MyBase() {};
    	virtual ~MyBase() {};
    };
    
    class MyBool : public MyBase
    {
    public:
    	MyBool(bool b) : b(b) {};
    
    public:
    	operator bool() const { return b; };
    
    private:
    	bool b;
    };
    
    class MyInt : public MyBase
    {
    public:
    	MyInt(int i) : i(i) {};
    
    public:
    	operator int() const { return i; };
    
    private:
    	int i;
    };
    
    class MyString : public MyBase
    {
    public:
    	MyString(const std::string& s) : s(s) {};
    
    public:
    	operator const char*() const { return s.c_str(); };
    
    private:
    	std::string s;
    };
    
    class Test : public MyBase
    {
    public:
    	Test() {};
    
    public:
    	void Setter(const MyBool* b, const MyInt* i, const MyString* s)
    	{
    		printf("MyBool:\t%d\n", (bool)(*b));
    		printf("MyInt:\t\t%d\n", (int)(*i));
    		printf("MyString:\t\t%s\n", (const char*)(*s));
    	}
    };
    

    Jetzt soll zur Laufzeit über z. B. einen Funktionszeiger diese Setter Funktion aufgerufen werden können.
    Also so eine Art Pseudo-Reflection.
    Dazu gibt es wiederum eine "MethodHandle" Funktion mit einer recht generischen "Call" Funktion mit folgender Syntax:

    MethodHandle::Call(MyBase* obj, const vector<MyBase*>& args)
    {
            // Some magic to call e.g.: obj->Setter(
                                                                          dynamic_cast<MyBool*>(args[0])
                                                                          dynamic_cast<MyInt*>(args[1]),
                                                                          dynamic_cast<MyString*>(args[2]));
    }
    

    Beschränken wir uns für das Beispiel mal drauf, dass alle Argumente als Basisklassen-Pointer übergeben werden.
    Die Problemstellung ist nun, dass ich zuerst einmal die Setter Funktion in irgendeiner Form dem Reflection System bekannt machen muss z.B. als Funktionspointer. Dazu ist die "MethodHandle" Klasse da. Und dann soll die "MethodHandle::Call" Funktion für das übergebene Objekt den Funktionspointer mit den angegebenen Argumenten aufrufen.
    Erschwerend hinzu kommt, dass die Argumente nur als Basisklassen Pointer vorliegen und für den Funktionspointer sicher wieder in das korrekte Objekt gecastet werden müssen.

    Meine Idee war nun eine Template Helferfunktion evtl. in Kombination mit einer Lambda-Funktion und Parameter Packs zu bauen, die anhand der Setter MemberFunktions Signatur eine HelferFunktion generiert die u. A. alle nötigen casts erledigt. Diese Helfer Funktion hätte die selbe Signatur wie die "MethodHandle::Call" Funktion und würde von letzterer dann auch aufgerufen. Folgender Codeschnipsel war schonmal eine gute Inspiration:

    template <typename T, T> struct CallHelper;
    
    template <typename T, typename R, typename ...Args, R (T::*mf)(Args...)>
    struct CallHelper<R (T::*)(Args...), mf>
    {
    	static R Call(T& obj, Args&&... args)
    	{
    		return (obj.*mf)(args...);
    	}
    };
    

    Da fehlt aber noch der Teil der die Argumente an die Funktionspointer Signatur anpasst/castet.
    Im Prinzip brauche ich für das konkrete Beispiel oben eine Helferfunktion die die Signatur

    Call(MyBase* obj, const MyBase* b, const MyBase* i, const MyBase* s)
    

    nach

    Call(MyBase* obj, const MyBool* b, const MyInt* i, const MyString* s)
    

    übersetzt. Diese HelferFunktion sollte sich im Idealfall direkt aus der Signatur der Setter MemberFunktion ergeben. Z.B. sollte im Code ein Macro

    #declare DECLARE_MEMBER_FUNCTION(FUNC_POINTER) \
            static MethodHandle g_handle = CallHelper<>(FUNC_POINTER);
    
    DECLARE_MEMBER_FUNTION(Test::Setter);
    DECLARE_MEMBER_FUNTION(Test::Getter);
    ...
    

    genügen damit die Helferfunktion generiert wird.
    Halt etwas generischer, damit man auch beliebige Anzahl an Argumenten oder auch andere Return Values händeln kann.
    Jemand eine Idee wie man das anstellen könnte?



  • @Enumerator sagte in Template für Memberfunktionspointer:

    Da fehlt aber noch der Teil der die Argumente an die Funktionspointer Signatur anpasst/castet.
    Im Prinzip brauche ich für das konkrete Beispiel oben eine Helferfunktion die die Signatur

    Call(MyBase* obj, const MyBase* b, const MyBase* i, const MyBase* s)
    

    nach

    Call(MyBase* obj, const MyBool* b, const MyInt* i, const MyString* s)
    

    übersetzt. Diese HelferFunktion sollte sich im Idealfall direkt aus der Signatur der Setter MemberFunktion ergeben. Z.B. sollte im Code ein Macro

    Ich würde es zuerst mit so einem Ansatz probieren

    #include <string>
    #include <iostream>
    #include <cstdio>
    
    class MyBase {
    public:
    	MyBase() {};
    	virtual ~MyBase() {};
    };
    
    class MyBool : public MyBase {
    	bool b_;
    public:
    	MyBool(bool b) : b_(b) {};
    
    	operator bool() const {
    		return b_;
    	};
    };
    
    class MyInt : public MyBase {
    	int i;
    public:
    	MyInt(int i) : i(i) {};
    
    	operator int() const {
    		return i;
    	};
    };
    
    class MyString : public MyBase {
    	std::string s;
    public:
    	MyString(const std::string& s) : s(s) {};
    
    	operator const char*() const {
    		return s.c_str();
    	};
    };
    
    class Test : public MyBase {
    public:
    	Test() {};
    
    	void Setter(const MyBool* b, const MyInt* i, const MyString* s)
    	{
    		printf("MyBool:\t%d\n", (bool)(*b));
    		printf("MyInt:\t\t%d\n", (int)(*i));
    		printf("MyString:\t\t%s\n", (const char*)(*s));
    	}
    };
    
    template <typename R, typename T1, typename T2, typename T3, typename T4>
    R func (MyBase* o1, MyBase* o2, MyBase* o3, MyBase* o4) {
    	T1* ot1 = dynamic_cast<T1*>(o1);
    	T2* ot2 = dynamic_cast<T2*>(o2);
    	T3* ot3 = dynamic_cast<T3*>(o3);
    	T4* ot4 = dynamic_cast<T4*>(o4);
    
    	return ot1->Setter(ot2,ot3,ot4);
    }
    
    int main () {
    	MyBool		b (true);
    	MyInt		i (1);
    	MyString	s ("Hello World!");
    	Test 		t;
    
    	func<void,Test,MyBool,MyInt,MyString>(&t,&b,&i,&s);
    }
    

    Kleines Update, so geht das ganze noch besser

    #include <string>
    #include <iostream>
    #include <cstdio>
    
    class MyBase {
    public:
    	MyBase() {};
    	virtual ~MyBase() {};
    };
    
    class MyBool : public MyBase {
    	bool b_;
    public:
    	MyBool (const bool b) : b_(b) {};
    
    	operator bool() const {
    		return b_;
    	}
    };
    
    class MyInt : public MyBase {
    	int i_;
    public:
    	MyInt (const int i) : i_(i) {};
    
    	operator int() const {
    		return i_;
    	}
    };
    
    class MyString : public MyBase {
    	std::string s_;
    public:
    	MyString (const std::string& s) : s_(s) {};
    
    	operator const char*() const {
    		return s_.c_str();
    	}
    };
    
    class Test : public MyBase {
    public:
    	Test() = default;
    
    	void Setter(const MyBool* b, const MyInt* i, const MyString* s) {
    		printf("MyBool:\t%d\n", (bool)(*b));
    		printf("MyInt:\t\t%d\n", (int)(*i));
    		printf("MyString:\t\t%s\n", (const char*)(*s));
    	}
    };
    
    class Test2 : public MyBase {
    public:
    	Test2() = default;
    
    	void Print (const MyBool* b) {
    		std::cout << "MyBool:\t"  << *b << "\n";
    	}
    };
    
    template <typename R, typename Base, typename... Types>
    R func3 (Base* b, R (Base::* f)(Types... args),Types... args) {
    
    	return (b->*f)(args...);
    }
    
    int main () {
    	MyBool		b (true);
    	MyInt		i (1);
    	MyString	s ("Hello World!");
    	Test 		t;
    	Test2		t2;
    
    	func3<void,Test,const MyBool*,const MyInt*, const MyString*>(&t,&Test::Setter,&b,&i,&s);
    	func3<void,Test2,const MyBool*>(&t2,&Test2::Print,&b);
    }
    
    


  • Hi, danke für den Denkanstoß. Bin jetzt auf eine Lösung gekommen:

    template <class T>
    inline T& ConvertHelper(T& value)
    {
    	return value;
    }
    
    template <class U, class T>
    inline U& ConvertHelper(T& value)
    {
    	return dynamic_cast<U&>(value);
    }
    
    template <typename R, typename Cls, typename ...FArgs, typename ...CArgs>
    R Invoke(R (Cls::*func)(FArgs...), Cls& cls, CArgs&&... args)
    {
    	return (cls.*func)(ConvertHelper<>(std::forward<CArgs>(args))...);
    }
    template <typename R, typename Cls1, typename Cls2, typename ...FArgs, typename ...CArgs>
    R Invoke(R (Cls1::*func)(FArgs...), Cls2* cls, CArgs&&... args)
    {
    	return (cls->*func)(ConvertHelper<>(std::forward<CArgs>(args))...);
    }
    

    Ich hatte die ganze Zeit versucht das Parameter Pack mittels

    (args, ...)
    

    zu entpacken. Ich dachte das Komma braucht man bei Komma getrennten Werten.
    Tatsächlich hätte ich einfach

    args...
    

    schreiben sollen. Ein Problem habe ich aber noch.
    Ich benenne in meinen Klassen Getter u. Setter identisch. Z. B.

    class Point
    {
         double X() const { ... };
         void X(double x) { ... };
    };
    

    Damit wird aber immer der Setter angezogen:

    double d1 = 0.0;
    Point point;
    Invoke(&Point::X, point, d1); // Setter
    //d1 = Invoke(&Point::X, point); // Getter (geht nicht)
    

    Kann man da explizit die komplette Funktionszeiger Signatur mittels z.B. static_cast vorgeben, damit der Compiler die richtige Variante verwendet? Habe es noch nicht hinbekommen



  • @Enumerator sagte in Template für Memberfunktionspointer:

    double d1 = 0.0;
    Point point;
    Invoke(&Point::X, point, d1); // Setter
    //d1 = Invoke(&Point::X, point); // Getter (geht nicht)
    

    Kann man da explizit die komplette Funktionszeiger Signatur mittels z.B. static_cast vorgeben, damit der Compiler die richtige Variante verwendet? Habe es noch nicht hinbekommen

    Casten ergibt da keinen Sinn. Was Du machen kannst, ist das Template explizit zu instanzieren.

    /// ... die drei Punkte stehen für die restlichen Typen, die Du noch ergänzen musst.
    d1 = Invoke<double, ... >(&Point::X, point);
    

    damit wird dann die automatische Typsuche abgeschaltet, und er nimmt die richtige Member Funktion.



  • @Enumerator sagte in Template für Memberfunktionspointer:

    Kann man da explizit die komplette Funktionszeiger Signatur mittels z.B. static_cast vorgeben, damit der Compiler die richtige Variante verwendet?

    Ja.
    Nur dein Template wird die const Memberfunktion nicht verdauen. Deswegen gibt es auch keinen Fehler wegen Uneindeutigkeit, weil ja nur eine der beiden Memberfunktionen in Frage kommt - die ohne const.

    Der Cast sollte so aussehen:

    d1 = Invoke(static_cast<double(Point::*)()const>(&Point::X, point));
    

    Zusätzlich musst du ein 2. Funktionstemplate machen das const Memberfunktionen akzeptiert.



  • Ok, das const in der member function hatte ich nicht beachtet. Persönlich mag ich die Variante mit static_cast gar nicht, und es geht auch anders. Da da Rest (...) gleich ist, nur die Änderungen zur anderen Version. Das ist nun mit Konzepten umgesetzt, es sollte sich aber auch mit std::enable_if_t lösen lassen.

    ...
    #include <type_traits>
    
    
    ...
    class NF;
    class CF;
    
    template <typename T>
    concept CheckFunction = std::same_as<T, NF>;
    
    template <typename T>
    concept CheckConstantFunction = std::same_as<T, CF>;
    
    template <typename Function, typename R, typename Base, typename ... Types>
    requires CheckConstantFunction<Function>
    R func (Base* b, R (Base::* f)(Types ... args) const, Types ... args) {
    	return (b->*f)(args ...);
    }
    
    template <typename Function, typename R, typename Base, typename ... Types>
    requires CheckFunction<Function>
    R func (Base* b, R (Base::* f)(Types ... args), Types ... args) {
    	return (b->*f)(args ...);
    }
    
    
    int main () {
    ...
    	func<CF,double,Test>(&t,&Test::Setter);
    	func<NF,void,Test2,const MyBool*>(&t2,&Test2::Print,&b);
    }
    
    

Anmelden zum Antworten