functor_traits und lambda_traits



  • "muss jedoch nur max. 20 Argumente unterstützen, den Code kann ich mir dann generieren lassen". Geht also auch um Funktoren und Lambdas mit Parametern (parameter_count würde ansonsten ja auch gar keinen Sinn ergeben).

    Was es können soll steht im Code schon etwa drin. Einsetzen kann man es z.B. in etwa so:

    template<typename T>
    struct result_of
    {
    	typedef typename conditional<function_traits<T>::is_function, function_traits<T>::return_type,
    			typename conditional<method_traits<T>::is_method, method_traits<T>::return_type,
    			typename conditional<functor_traits<T>::is_functor, functor_traits<T>::return_type, /*Felertyp*/>::type>::type>::type type;
    };
    
    template<typename T>
    struct varcall
    {
    	T fnc;
    
    	explicit varcall(T fnc) : fnc(fnc) {}
    
    	operator typename result_of<T>::type () { return fnc(); }
    };
    
    template<typename T>
    varcall<T> fnc_to_var(T fnc)
    {
    	return varcall<T>(fnc);
    }
    
    int main()
    {
    	std::vector<int> vec(1000);
    	std::fill(vec.begin(), vec.end(), fnc_to_var(&std::rand)); //Ich kenne std::generate, das ist nur ein Anwendungsbeispiel
    }
    

  • Mod

    Zudem weiß ich nicht, ob declval mit einem Lambdatypen erlaubt ist.

    Hier liegt dein Denkproblem - ein Lambda ist nichts weiter als ein Klassenobjekt.
    Du kannst nicht prüfen ob ein Funktionsobjekt ein Lambda ist - genauso wenig wie du prüfen kannst ob ein Funktionsobjekt-Typ ein closure-Typ ist.

    Dein Code ist auch teils falsch - was wenn decltype(std::declval<U>()()) eine Referenztyp ist?

    Um zu prüfen ob ein Typ eine Klasse mit überladenem operator() ist, nutz sowas:

    template<typename U>
    	static std::true_type check(decltype(&U::operator()));
    
    	template<typename>
    	static std::false_type check(...);
    
    public:
    
    	static const bool is_functor = decltype(check<T>(0))::value;
    

    Es gibt aber selbstverständlich auch Probleme mit diesem Code - zum Beispiel könnte der operator() auch ein Funktionstemplate sein. Es ist AFAIK unmöglich jeden Funktionsobjekt-Typ als solchen feststellen zu können, selbst wenn man sich auf eine Parameterzahl beschränkt.

    Falls du also den Template-fall weglässt lässt sich functor_traits recht einfach (durch partielle Spezialisierungen) implementieren.



  • Arcoth schrieb:

    Hier liegt dein Denkproblem - ein Lambda ist nichts weiter als ein Klassenobjekt.

    Ist es daher instanziierbar?

    Arcoth schrieb:

    Du kannst nicht prüfen ob ein Funktionsobjekt ein Lambda ist - genauso wenig wie du prüfen kannst ob ein Funktionsobjekt-Typ ein closure-Typ ist.

    Was ist ein closure-Typ?

    Arcoth schrieb:

    Dein Code ist auch teils falsch - was wenn decltype(std::declval<U>()()) eine Referenztyp ist?

    Stimmt, daran dachte ich nicht. Macht std::remove_reference den Trick?

    Arcoth schrieb:

    Um zu prüfen ob ein Typ eine Klasse mit überladenem operator() ist, nutz sowas:

    template<typename U>
    	static std::true_type check(decltype(&U::operator()));
    
    	template<typename>
    	static std::false_type check(...);
    
    public:
    
    	static const bool is_functor = decltype(check<T>(0))::value;
    

    Geht das irgendwie auch in C++03?

    Arcoth schrieb:

    Es gibt aber selbstverständlich auch Probleme mit diesem Code - zum Beispiel könnte der operator() auch ein Funktionstemplate sein.

    Stimmt, daran dachte ich ebenfalls nicht.

    Arcoth schrieb:

    Falls du also den Template-fall weglässt lässt sich functor_traits recht einfach (durch partielle Spezialisierungen) implementieren.

    Auch in C++03? Könntest du das in groben Zügen zeigen? Ich kann's mir leider echt nicht vorstellen.


  • Mod

    Macht std::remove_reference den Trick?

    Das gibt es aber doch erst seit C++11.

    Was ist ein closure-Typ?

    Der Typ eines Lambdas - also die Klasse dessen Objekt es ist.

    Geht das irgendwie auch in C++03?

    Wie wärs mit

    template <void*>
        struct Nothing
        {
            typedef char type;
        };
    
        template <typename U>
        static typename Nothing<&U::operator()>::type check(int);
    
        template <typename>
        static int check(char);
    
    public:
    
        static const bool is_functor = sizeof(check<T>(0)) == 1;
    

    Könntest du das in groben Zügen zeigen?

    Ist eklig mit C++03. Hast du Boost.PP zur Verfügung?



  • Arcoth schrieb:

    Macht std::remove_reference den Trick?

    Das gibt es aber doch erst seit C++11.

    Und nahezu trivial zu implementieren, für vor C++11.



  • Arcoth schrieb:

    Macht std::remove_reference den Trick?

    Das gibt es aber doch erst seit C++11.

    Joa schon, ich habe es aber auch in C++03 implementiert mit partieller Spezialisierung.

    Arcoth schrieb:

    Wie wärs mit

    template <void*>
        struct Nothing
        {
            typedef char type;
        };
     
        template <typename U>
        static typename Nothing<&U::operator()>::type check(int);
     
        template <typename>
        static int check(char);
     
    public:
     
        static const bool is_functor = sizeof(check<T>(0)) == 1;
    

    Sieht zwar sehr gut aus, geht aber leider irgendwie nicht: http://ideone.com/LEJVUl
    Mache ich etwas falsch?

    Arcoth schrieb:

    Könntest du das in groben Zügen zeigen?

    Ist eklig mit C++03. Hast du Boost.PP zur Verfügung?

    Ist egal ob's eklig wird oder nicht. Boost wollte ich eigentlich nicht verwenden (einiges davon gibt's da ja eh schon fertig glaube ich).



  • Du kannst keine Funktionspointer implizit nach void* konvertieren.
    Wieso so check-Dinger nicht gehen, kannste gut erkennen indem du den default-Fall deaktivierst.
    Übrigens: int kann genauso groß sein wie char.
    Nimm besser char und char[2] oder so.


  • Mod

    Arcoth schrieb:

    Wie wärs mit

    template <void*>
        struct Nothing
        {
            typedef char type;
        };
     
        template <typename U>
        static typename Nothing<&U::operator()>::type check(int);
     
        template <typename>
        static int check(char);
     
    public:
     
        static const bool is_functor = sizeof(check<T>(0)) == 1;
    

    Weder Funktionszeiger noch Zeiger auf Member sind implizit in void* konvertierbar.
    Schnell hingeschrieben könnte es so funktionieren. Kann man noch etwas vereinfachen.


  • Mod

    camper schrieb:

    Schnell hingeschrieben könnte es so funktionieren.

    Völlig irre! Warum sind die Rückgabetypen Referenzen?

    Ich schlage als korrigierte Version

    template<typename T>
    class test
    {
    	template <int>
    	struct Nothing {typedef char type;};
    
    	template <typename U>
    	static typename Nothing<sizeof(&U::operator())>::type check(int);
    
    	template <typename>
    	static int check(char);
    
    public:
    
    	static const bool is_functor = sizeof(check<T>(0)) == 1;
    };
    

    vor.



  • template<typename T>
    class test
    {
    	template <int>//int rein => char raus.
    	struct Nothing {typedef char type;};
    
    	template <typename U>//int rein => char raus.
    	static typename Nothing<sizeof(&U::operator())>::type check(int);
    
    	template <typename>//char rein => int raus.
    	static int check(char);
    
    public:
    
    	static const bool is_functor = sizeof(check<T>(0)) == 1;
    };
    


  • volkard schrieb:

    template<typename T>
    class test
    {
    	template <int>//int rein => char raus.
    	struct Nothing {typedef char type;};
    
    	template <typename U>//int rein => char raus.
    	static typename Nothing<sizeof(&U::operator())>::type check(int);
    
    	template <typename>//char rein => int raus.
    	static int check(char);
    
    public:
    
    	static const bool is_functor = sizeof(check<T>(0)) == 1;
    };
    

    ?
    Das ist Absicht.



  • Nathan schrieb:

    Das ist Absicht.

    Ist es notwendig?



  • volkard schrieb:

    Nathan schrieb:

    Das ist Absicht.

    Ist es notwendig?

    Ja.
    Der unterschiedlich Rückgabewert weil sonst sizeof auf den Rückgabewert nicht funktioniert um die ausgewählte Funktion zu bestimmen und der unterschiedliche Paramter, damit die char-Version wenn man sie mit einem int aufruft, geringere Priorität wegen der Konvertierung nach char hat, als die andere Funktion, die man direkt mit einem int aufrufen kann. Ansonsten hätte man eine Ambiguität.



  • Nathan schrieb:

    volkard schrieb:

    Nathan schrieb:

    Das ist Absicht.

    Ist es notwendig?

    Ja.
    Der unterschiedlich Rückgabewert weil sonst sizeof auf den Rückgabewert nicht funktioniert um die ausgewählte Funktion zu bestimmen und der unterschiedliche Paramter, damit die char-Version wenn man sie mit einem int aufruft, geringere Priorität wegen der Konvertierung nach char hat, als die andere Funktion, die man direkt mit einem int aufrufen kann. Ansonsten hätte man eine Ambiguität.

    Danke, daß Du jetzt neben Arcoth auch mich für einen Vollidionten hältst.
    Und jetzt antworte nochmal gescheit, warum Du zweimal int/char vertauscht, mir scheint der Doppeltausch nichts zu bewirken, außer die Lesbarkeit zu senken.



  • volkard schrieb:

    Nathan schrieb:

    volkard schrieb:

    Nathan schrieb:

    Das ist Absicht.

    Ist es notwendig?

    Ja.
    Der unterschiedlich Rückgabewert weil sonst sizeof auf den Rückgabewert nicht funktioniert um die ausgewählte Funktion zu bestimmen und der unterschiedliche Paramter, damit die char-Version wenn man sie mit einem int aufruft, geringere Priorität wegen der Konvertierung nach char hat, als die andere Funktion, die man direkt mit einem int aufrufen kann. Ansonsten hätte man eine Ambiguität.

    Danke, daß Du jetzt neben Arcoth auch mich für einen Vollidionten hältst.

    Äh, moment.
    Arcoth halte ich nicht für einen Vollidioten, ich dachte der fehlerhafte Code den Abrakadabra zitiert hatte, stammte von ihm, als ich meinen Beitrag geschrieben habe, da ich den von ihm nur überflogen hatte. Das Arcoth den Fehler gemacht hat, fiel mir nicht auf, weswegen ich meine Antwort halt so formuliert.
    Und dich halte ich auch nicht für einen Vollidioten. Ich hatte mich schon gewundert, dass du das gefragt hast*, habs aber normal beantwortet, weil ich dachte, dass du auf etwas hinauswillst, was du ja scheinbar auch wolltest. Ich hatte gerechnet, dass du mich jetzt total bloßstellst, weil ich irgendetwas nicht wusste.

    Und jetzt antworte nochmal gescheit, warum Du zweimal int/char vertauscht, mir scheint der Doppeltausch nichts zu bewirken, außer die Lesbarkeit zu senken.

    Der Code ist von Arcoth, nicht von mir.
    Und was meinst du mit "Doppeltausch"?

    *Siehe mein Fragezeichen, bevor ich "Das ist Absicht" schrieb.



  • Sorry, ambiq formuliert, gemein war "daß nach Arcoth auch Du mich für".

    Nathan schrieb:

    weil ich dachte, dass du auf etwas hinauswillst,

    Mich hat's verwirrt, daß int/char zweimal vertauscht werden und dachte, wenn man das vermeiden würde, müßte man nur noch halb so viel sich im Kopf merken, um zu verstehen, wie der Code geht. Vielleicht bin ich noch nicht richtig wach heute, aber irgendwie scheint mir der "Doppeltausch" ungünstig zu sein.

    template<typename T>
    class test
    {
        template <int>//int rein => int raus.
        struct Nothing {typedef int type;};
    
        template <typename U>//int rein => int raus.
        static typename Nothing<sizeof(&U::operator())>::type check(int);
    
        template <typename>//char rein => char raus.
        static char check(char);
    
    public:
    
        static const bool is_functor = sizeof(check<T>(0)) != 1;
    };
    


  • volkard schrieb:

    Sorry, ambiq formuliert, gemein war "daß nach Arcoth auch Du mich für".

    Nathan schrieb:

    weil ich dachte, dass du auf etwas hinauswillst,

    Mich hat's verwirrt, daß int/char zweimal vertauscht werden und dachte, wenn man das vermeiden würde, müßte man nur noch halb so viel sich im Kopf merken, um zu verstehen, wie der Code geht. Vielleicht bin ich noch nicht richtig wach heute, aber irgendwie scheint mir der "Doppeltausch" ungünstig zu sein.

    template<typename T>
    class test
    {
        template <int>//int rein => int raus.
        struct Nothing {typedef int type;};
    
        template <typename U>//int rein => int raus.
        static typename Nothing<sizeof(&U::operator())>::type check(int);
    
        template <typename>//char rein => char raus.
        static char check(char);
    
    public:
    
        static const bool is_functor = sizeof(check<T>(0)) != 1;
    };
    

    Ach so, das meinst du. Die int Version sollte auch int nehmen und die char Version char.
    Ja, da stimme ich dir zu, ich habe das Problem bei meinem generischem "Ausdruck gültig?" folgendermaßen umgangen:

    template <typename T, typename Expr>
    class expression_valid
    {
        // char und int haben keinerlei semantische Bedeutung und können außerdem noch gleich groß sein
        using matched_t     = char(&)[1];
        using unmatched_t   = char(&)[2];
    
        template <class T2>
        static matched_t checker(decltype(Expr::template check<T2>())) noexcept;
    
        // ellipsis als default, das ist aktuell noch der einzig anehmbare Verwendungszweck in C++ und außerdem garantiert das schlechteste Match
        template <class T2>
        static unmatched_t checker(...) noexcept;
    
    public:
        // weitaus mehr Semantik als == 1
        // besser wäre sogar noch is_same, aber gut
        static constexpr auto value = sizeof(checker<T>(0)) == sizeof(matched_t);
    };
    

  • Mod

    @volkard Niemand hält dich für einen Idioten.

    Vielleicht bin ich noch nicht richtig wach heute, aber irgendwie scheint mir der "Doppeltausch" ungünstig zu sein.

    Das ist mir direkt aufgefallen nachdem ich den Code geschrieben habe - die Tatsache dass das irgendjemand benörgeln wird war mir auch sofort bewusst, aber ich war einfach zu faul es zu korrigieren.

    @Nathan Für matched_t & co. etwas benutze ich das oder ähnliches. Fasst du so etwas nicht zusammen?
    Und war es wirklich nötig checker als noexcept zu markieren?

    Endlich ist mir bei Nathans Version aufgefallen warum die Rückgabetypen Referenzen sind - wären ja sonst beides Zeiger.



  • Arcoth schrieb:

    @Nathan Für matched_t & co. etwas benutze ich das oder ähnliches. Fasst du so etwas nicht zusammen?

    Ich brauchte zu dem Zeitpunkt nur zwei einfache Typedefs unterschiedlicher Größe, da kam ich gar nicht auf die Idee das zu generalisieren.
    Dein rank Ding ist aber nice.

    Und war es wirklich nötig checker als noexcept zu markieren?

    Gewohnheit. Ich war damals (vor 6 Monaten oder so) in der Phase, wo noexcept mein Default für Funktionen war.
    Für neueren Code hab ich meine Exceptionspolicies geändert und somit auch mein noexcept. Ich nutz das jetzt nur aus semantischen Gründen, für Funktionen die nie schief gehen können (Ein operator[] ist bspw. nicht noexcept*, selbst wenn er nicht den Index checkt, man kann einen Segfault kriegen, d.h. die Funktion failen).

    Endlich ist mir bei Nathans Version aufgefallen warum die Rückgabetypen Referenzen sind - wären ja sonst beides Zeiger.

    Da hast du mir was voraus. Ich musste beim Copy-Pasten erst einmal ewig überlegen, wieso Referenzen.

    *Genau genommen ist der idR ein spezielles NOFAIL, was "geht nur schief, wenn Programmierer dumm ist" bedeutet und je nachdem ob Logikfehler gecheckt werden noexcept oder nicht ist, aber darum gehts hier nicht.


  • Mod

    Genau genommen ist der idR ein spezielles NOFAIL, was "geht nur schief, wenn Programmierer dumm ist" bedeutet

    😃 😃

    man kann einen Segfault kriegen,

    Du tust da aber entsprechende Assertions rein, richtig?


Anmelden zum Antworten