web-dev-qa-db-fra.com

Comment fonctionne le paramètre de modèle de std :: function? (la mise en oeuvre)

Dans la page d'accueil de Bjarne Stroustrup ( FAQ C++ 11 ):

struct X { int foo(int); };

std::function<int(X*, int)> f;
f = &X::foo; //pointer to member

X x;
int v = f(&x, 5); //call X::foo() for x with 5

Comment ça marche? Comment std :: function appelle une fonction membre foo ?

Le paramètre de modèle est int(X*, int), est &X::foo Converti du pointeur de fonction membre en un pointeur de fonction non membre ?!

(int(*)(X*, int))&X::foo //casting (int(X::*)(int) to (int(*)(X*, int))

Pour clarifier: je sais que nous n'avons pas besoin de lancer de pointeur pour utiliser std :: function , mais je ne sais pas comment les internes de std :: function gère cette incompatibilité entre un pointeur de fonction membre et un pointeur de fonction non membre . Je ne sais pas comment la norme nous permet d'implémenter quelque chose comme std :: function !

54
M. Sadeq H. E.

Après avoir obtenu de l'aide d'autres réponses et commentaires, et lu le code source de GCC et la norme C++ 11, j'ai trouvé qu'il était possible d'analyser un type de fonction (son type de retour et ses types d'arguments ) en utilisant spécialisation partielle du modèle et surcharge de fonction.

Voici un exemple simple (et incomplet) pour implémenter quelque chose comme std::function:

template<class T> class Function { };

// Parse the function type
template<class Res, class Obj, class... ArgTypes>
class Function<Res (Obj*, ArgTypes...)> {
    union Pointers {
        Res (*func)(Obj*, ArgTypes...);
        Res (Obj::*mem_func)(ArgTypes...);
    };

    typedef Res Callback(Pointers&, Obj&, ArgTypes...);

    Pointers ptrs;
    Callback* callback;

    static Res call_func(Pointers& ptrs, Obj& obj, ArgTypes... args) {
        return (*ptrs.func)(&obj, args...);
    }

    static Res call_mem_func(Pointers& ptrs, Obj& obj, ArgTypes... args) {
        return (obj.*(ptrs.mem_func))(args...);
    }

  public:

    Function() : callback(0) { }

    // Parse the function type
    Function(Res (*func)(Obj*, ArgTypes...)) {
        ptrs.func = func;
        callback = &call_func;
    }

    // Parse the function type
    Function(Res (Obj::*mem_func)(ArgTypes...)) {
        ptrs.mem_func = mem_func;
        callback = &call_mem_func;
    }

    Function(const Function& function) {
        ptrs = function.ptrs;
        callback = function.callback;
    }

    Function& operator=(const Function& function) {
        ptrs = function.ptrs;
        callback = function.callback;
        return *this;
    }

    Res operator()(Obj& obj, ArgTypes... args) {
        if(callback == 0) throw 0; // throw an exception
        return (*callback)(ptrs, obj, args...);
    }
};

Usage:

#include <iostream>

struct Funny {
    void print(int i) {
        std::cout << "void (Funny::*)(int): " << i << std::endl;
    }
};

void print(Funny* funny, int i) {
    std::cout << "void (*)(Funny*, int): " << i << std::endl;
}

int main(int argc, char** argv) {
    Funny funny;
    Function<void(Funny*, int)> wmw;

    wmw = &Funny::print; // void (Funny::*)(int)
    wmw(funny, 10); // void (Funny::*)(int)

    wmw = &print; // void (*)(Funny*, int)
    wmw(funny, 8); // void (*)(Funny*, int)

    return 0;
}
34
M. Sadeq H. E.

La façon dont il le fait (je crois) n'est pas définie (mais je n'ai pas de copie de la norme ici).

Mais étant donné toutes les différentes possibilités qui doivent être couvertes, j'ai le sentiment que déchiffrer la définition exacte de son fonctionnement serait très difficile: je ne vais donc pas essayer.

Mais je pense que vous aimeriez savoir comment fonctionnent les foncteurs et ils sont relativement simples. Voici donc un exemple rapide.

Functors:

Ce sont des objets qui agissent comme des fonctions.
Ils sont très utiles dans le code du modèle car ils vous permettent souvent d'utiliser des objets ou des fonctions de manière interchangeable. Le grand avantage des foncteurs, c'est qu'ils peuvent tenir l'état (une sorte de fermeture du pauvre).

struct X
{
     int operator()(int x) { return doStuff(x+1);}
     int doStuff(int x)    { return x+1;}
};

X   x;  // You can now use x like a function
int  a = x(5);

Vous pouvez utiliser le fait que l'état de maintien du foncteur contient des éléments comme les paramètres ou les objets ou le pointeur sur les méthodes membres (ou toute combinaison de ceux-ci).

struct Y // Hold a member function pointer
{
    int (X::*member)(int x);
    int operator(X* obj, int param) { return (obj->*member)(param);}
};
X  x;
Y  y;
y.member = &X::doStuff;
int a = y(&x,5);

Ou même aller plus loin et lier des paramètres. Alors maintenant, tout ce que vous devez fournir est l'un des paramètres.

struct Z
{
    int (X::*member)(int x);
    int  param;
    Z(int (X::*m)(int), int p) : member(m), param(p) {}

    int operator()(X* obj)  { return (obj->*member)(param);}
    int operator()(X& obj)  { return (obj.*member)(param);}
};

Z z(&X::doStuff,5);

X x;
int a = z(x);
3
Martin York

g ++ semble avoir une union qui peut conserver soit un pointeur de fonction, un pointeur de membre ou un pointeur vide qui pointe probablement vers un foncteur. Ajoutez des surcharges qui signalent de manière appropriée quel membre du syndicat est valide et lourd à couler dans une soupe, puis cela fonctionne ...

2
Tomek

Ce ne sont pas des pointeurs de fonction. C'est pour ça que la fonction std :: existe. Il enveloppe tous les types appelables que vous lui donnez. Vous devriez vérifier boost :: bind - il est souvent utilisé pour rendre les pointeurs de fonctions membres appelables en tant que (ceci, args).

1
Puppy

Pour répondre à la question dans le titre. Le paramètre que std::function uses est une bonne astuce pour passer de nombreux paramètres en tant que paramètre de modèle unique. Ces arguments étant les types d'argument et le type de retour d'une fonction.

Il coïncide que std::function essaie de taper un foncteur général mais ce n'est qu'une coïncidence.

En fait, il était une fois des compilateurs qui n'acceptaient pas de telles astuces et les boost::function le précurseur avait une syntaxe portable par laquelle tous les paramètres pouvaient être passés séparément:

Syntaxe préférée

boost::function<void(int*, int, int&, float&)> sum_avg;

Syntaxe portable

boost::function4<void, int*, int, int&, float&> sum_avg;

https://www.boost.org/doc/libs/1_68_0/doc/html/function/tutorial.html#id-1.3.16.5.4

Voilà donc comment les paramètres de modèle de std::function travail, à la fin, c'est juste une astuce pour que beaucoup de paramètres ressemblent à un appel de fonction. Les pointeurs de fonction vers ce type de fonction sont pas nécessairement impliqués dans la classe.

1
alfC