¿Cómo funciona el parámetro de plantilla de std::function? (aplicación)


En La página principal de Bjarne Stroustrup (C++11 FAQ):

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

¿Cómo funciona? ¿Cómo llama std::function a una función miembro foo?

El parámetro de plantilla es int(X*, int), es &X::foo convertido del puntero de función miembro a un puntero de función no miembro ?!

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

Para aclarar: Sé que no necesitamos lanzar ningún puntero para usar std:: function , pero no se cómo funcionan las funciones internas de std::functionmaneja esta incompatibilidad entre un puntero de función miembro y un puntero de función no miembro . No se como el estándar nos permite implementar algo como std::function!

Author: Gyorgy Szekely, 2010-08-21

4 answers

Gracias por todas las respuestas.

Encontré un buen ejemplo de la sección 14.8.2.5-21 de la norma:

template<class> struct X { };
template<class R, class ... ArgTypes> struct X<R(int, ArgTypes ...)> { };
template<class ... Types> struct Y { };
template<class T, class ... Types> struct Y<T, Types& ...> { };
template<class ... Types> int f(void (*)(Types ...));
void g(int, float);
// uses primary template
X<int> x1;
// uses partial specialization; ArgTypes contains float, double
X<int(int, float, double)> x2;
// uses primary template
X<int(float, int)> x3;
// use primary template; Types is empty
Y<> y1;
// uses partial specialization; T is int&, Types contains float, double
Y<int&, float&, double&> y2;
// uses primary template; Types contains int, float, double
Y<int, float, double> y3;
// OK; Types contains int, float
int fv = f(g);

Dice que con la especialización de la plantilla, podemos analizar los parámetros de la plantilla de un tipo de función (impresionante)! El siguiente es un ejemplo sucio / simple sobre cómo la función std::puede funcionar:

template<class T> struct Function { };

template<class T, class Obj, class... Args>
struct Function<T(Obj*, Args...)> // Parsing the function type
{
    enum FuncType
    {
        FuncTypeFunc,
        FuncTypeMemFunc
    };
    union FuncPtr
    {
        T(*func)(Obj*, Args...);
        T(Obj::*mem_func)(Args...);
    };

    FuncType m_flag;
    FuncPtr m_func_ptr;

    Function(T(*func)(Obj*, Args...)) // void(*)(Funny*, int, int)
    {
        m_flag = FuncTypeFunc;
        m_func_ptr.func = func;
    }
    Function(T(Obj::*mem_func)(Args...)) // void(Funny::*)(int, int)
    {
        m_flag = FuncTypeMemFunc;
        m_func_ptr.mem_func = mem_func;
    }

    void play(Obj* obj, Args... args)
    {
        switch(m_flag)
        {
          case FuncTypeFunc:
            (*m_func_ptr.func)(obj, args...);
            break;
          case FuncTypeMemFunc:
            (obj->*m_func_ptr.mem_func)(args...);
            break;
        }
    }
};

Uso:

#include <iostream>

struct Funny
{
    void done(int i, int j)
    {
        std::cout << "Member Function: " << i << ", " << j << std::endl;
    }
};

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

int main(int argc, char** argv)
{
    Funny funny;
    Function<void(Funny*, int, int)> f = &Funny::done; // void(Funny::*)(int, int)
    Function<void(Funny*, int, int)> g = &done; // void(*)(Funny*, int, int)
    f.play(&funny, 5, 10); // void(Funny::*)(int, int)
    g.play(&funny, 5, 10); // void(*)(Funny*, int, int)
    return 0;
}

Editar: Gracias a Tomek por su buena pista sobre union s, el ejemplo anterior se cambia para mantener la función miembro/no miembro puntero en una variable (no dos).


Editar: Martin York tiene razón, la instrucción switch no era una buena idea en el ejemplo anterior, así que cambié el ejemplo completamente para que funcione mejor:

template<class T> class Function { };

template<class Res, class Obj, class... ArgTypes>
class Function<Res (Obj*, ArgTypes...)> // Parsing the function type
{
    union Pointers // An union to hold different kind of pointers
    {
        Res (*func)(Obj*, ArgTypes...); // void (*)(Funny*, int)
        Res (Obj::*mem_func)(ArgTypes...); // void (Funny::*)(int)
    };
    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...); // void (*)(Funny*, int)
    }
    static Res call_mem_func(Pointers& ptrs, Obj& obj, ArgTypes... args)
    {
        return (obj.*(ptrs.mem_func))(args...); // void (Funny::*)(int)
    }

  public:

    Function() : callback(0) { }
    Function(Res (*func)(Obj*, ArgTypes...)) // void (*)(Funny*, int)
    {
        ptrs.func = func;
        callback = &call_func;
    }
    Function(Res (Obj::*mem_func)(ArgTypes...)) // void (Funny::*)(int)
    {
        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...);
    }
};

Uso:

#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;
}
 31
Author: M. Sadeq H. E.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-09 23:00:56

Cómo lo hace (creo) se deja sin definir (pero no tengo una copia del estándar aquí).

Pero dadas todas las diferentes posibilidades que deben cubrirse, tengo la sensación de que descifrar la definición exacta de cómo funciona sería realmente difícil: Así que no voy a intentarlo.

Pero creo que le gustaría saber cómo funcionan los funtores y son relativamente simples. Así que aquí hay un ejemplo rápido.

Funtores:

Estos son objetos que actúan como función.
Son muy útiles en el código de plantilla, ya que a menudo le permiten usar objetos o funciones indistintamente. Sin embargo, lo mejor de los funtores es que pueden mantener el estado (una especie de cierre del hombre pobre).

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);

Puede usar el hecho de que el estado de retención del funtor contenga cosas como parámetros o los objetos o el puntero a los métodos miembro (o cualquier combinación de los mismos).

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);

O incluso ir más allá y enlazar parámetros. Así que ahora todo lo que necesita para proporcionar es uno de los parámetros.

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
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-15 09:56:33

G++ parece tener una unión que puede mantener cualquier puntero de función, puntero de miembro o puntero vacío que probablemente apunta a un funtor. Agregue sobrecargas que señalen apropiadamente qué miembro del sindicato es válido y pesado a una sopa y luego funcionará...

 2
Author: Tomek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-20 21:34:14

No son punteros de función. Para eso existe la función std::. Envuelve cualquier tipo de llamada que le des. Deberías revisar boost:: bind-a menudo se usa para hacer que los punteros de funciones miembro se puedan llamar como (this, args).

 1
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-20 21:31:53