c++ - ¿Por qué std:: bind no funciona sin marcadores de posición en este ejemplo(función miembro)?
function c++11 (2)
Por ejemplo, esta es mi función miembro ( do_it
):
class oops
{
public:
void do_it(GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("Hi there :)/n");
}
};
... y yo uso std::bind
para que se vea como una función que no es miembro:
oops o;
std::function<void(GtkWidget*, GdkEvent*, gpointer)> f = std::bind(&oops::do_it, o);
pero no funciona, el siguiente es el mensaje de error del compilador:
program.cc: In function ‘int main(int, char**)’:
program.cc:69:85: error: conversion from ‘std::_Bind_helper<false, void (oops::*)(_GtkWidget*, _GdkEvent*, void*), oops&>::type {aka std::_Bind<std::_Mem_fn<void (oops::*)(_GtkWidget*, _GdkEvent*, void*)>(oops)>}’ to non-scalar type ‘std::function<void(_GtkWidget*, _GdkEvent*, void*)>’ requested
std::function<void(GtkWidget*, GdkEvent*, gpointer)> f = std::bind(&oops::do_it, o);
^
Tengo que arreglarlo usando std::placeholders
:
oops o;
std::function<void(GtkWidget*, GdkEvent*, gpointer)> f = std::bind(&oops::do_it, o, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
¿Por qué no funciona sin especificar std::placeholders
?
Tenga en cuenta que los números de los marcadores de posición especifican el número del parámetro en el punto de llamada. El primer parámetro del punto de llamada se identifica con _1, el segundo con _2, y así sucesivamente.
Quiero agregar algunos ejemplos para @ Manu343726. Si intenta transformar una función con 3 parámetros en una función con 2 parámetros. Puede especificar 1 parámetro en el momento de la vinculación y proporcionar el resto 2 en el momento de la llamada.
typedef std::function<void(int, int)> add2;
int add3(int x1, int x2, int x3) { return x1 + x2 + x3; }
auto fn = std::bind(add3, 11, std::placeholders::_1, std::placeholders::_2);
fn(22, 33)
ahora 11 es x1 de add3, _1 es x2 de add3, _2 es x3 de add3. _2 significa que fn
tiene 2 parámetros para especificar en el momento de la llamada. Y este formato es incorrecto:
auto fn = std::bind(add3, 11, std::placeholders::_2, std::placeholders::_3);
std::bind()
está diseñado para hacer una entidad invocable que representa una llamada (parcial) a una función. Funciona vinculando algunos parámetros de la llamada al objeto de llamada generado, y dejando que el resto de los parámetros se especifiquen en el punto de la llamada:
void f(int,int,int);
int main()
{
std::function<void()> f_call = std::bind( f , 1 , 2 , 3);
f_call(); //Equivalent to f(1,2,3)
}
El primer parámetro de std::bind()
es la función a llamar, y el resto son los argumentos de la llamada.
En este ejemplo, el objeto de llamada se genera con los tres parámetros especificados, por lo que el punto de llamada no tiene parámetros. Ahora considere una llamada parcial definida:
std::function<void(int,int,int)> f_call = std::bind( f );
Esto no se compila, porque la función tiene tres parámetros y no ha especificado ninguno. Eso no tiene sentido, ¿verdad? Si tiene una función con tres parámetros, debe pasar tres parámetros al objeto de llamada.
Si necesita especificar que algunos parámetros deben especificarse en el punto de llamada, debe usar marcadores de posición para representar esos parámetros. Por ejemplo:
using namespace std::placeholders;
std::function<void(int,int,int)> f_call = std::bind( f , _1 , _2 , _3 );
f_call( 1 , 2 , 3 ); //Same as f(1,2,3)
Como puede ver, usamos marcadores de posición para especificar tres "espacios" para la llamada de función, es decir, tres parámetros que se especificarían en el punto de llamada.
Tenga en cuenta que los números de los marcadores de posición especifican el número del parámetro en el punto de llamada . El primer parámetro del punto de llamada se identifica con _1
, el segundo con _2
, y así sucesivamente. Esto podría usarse para especificar parámetros de diferentes maneras, reordenando los parámetros de una llamada de función, etc. Por ejemplo:
std::function<void(int,int)> f_call = std::bind( f , _1 , 2 , _2 );
f_call( 1 , 3 ); //Equivalent to f( 1 , 2 , 3 );
std::function<void(int,int,int)> reordered_call = std::bind( f , _3 , _2 , _1 );
reordered_call( 3 , 2 , 1 ); //Same as f( 1 , 2 , 3 );
Finalmente, std::bind()
podría usarse para enlazar una función miembro al objeto usado para llamarla:
struct foo
{
void f() const;
};
int main()
{
foo myfoo;
std::function<void()> f = std::bind( &foo::f , std::cref( myfoo ) );
f(); //Tah dah!
}
Una función miembro podría verse como una función con un parámetro oculto, que es el objeto con el que se realiza la llamada. Es por eso que el objeto se enlaza como primer parámetro.
Pero, exactamente como en los ejemplos anteriores, si solo conoce cierto número de parámetros en el punto de enlace y necesita especificar otros más adelante en el punto de llamada, debe usar marcadores de posición :
using namespace std::placeholders;
oops o;
std::function<GtkWidget*,GtkEvent*,gpointer> do_it = std::bind( &oops::do_it , std::ref( o ) , _1 , _2 , _3 );
do_it( /* first param */ , /*second param */ , /* third param */ ); //Call
Algunos detalles
La firma del objeto de llamada.
Tenga en cuenta que usamos la std::function
para almacenar el objeto de llamada. La firma de esa función depende del tipo de objeto de llamada generado, es decir, depende de la forma en que haya especificado los parámetros en el punto de enlace .
Un objeto de llamada es simplemente otra entidad invocable que actúa como la llamada a la función original. Siguiendo con nuestros ejemplos de funciones f()
:
std::function<void()> f_call = std:bind( f , 1 , 2 , 3 );
Aquí la firma del objeto de llamada es void()
, porque hemos especificado el conjunto de parámetros de agujero en el punto de enlace, y no queda nadie por especificar en el punto de llamada (por lo tanto, el objeto de llamada no tiene parámetros).
En el caso de una llamada parcial:
std::function<void(int,int,int)> f_call = std::bind( f, _1 , _2 , _3 );
f_call( 1 , 2 , 3 );
la firma del objeto de llamada es void(int,int,int)
, porque nos quedan tres parámetros para especificar en el punto de llamada (tenga en cuenta los marcadores de posición). En general, el objeto de llamada tiene la misma cantidad de parámetros que los marcadores de posición que especificó en el punto de enlace. .