web-dev-qa-db-fra.com

Pourquoi std :: bind ne fonctionne-t-il pas sans espaces réservés dans cet exemple (fonction membre)?

Par exemple, voici ma fonction membre (do_it):

class oops
{
public:
    void do_it(GtkWidget *widget, GdkEvent *event, gpointer data)
    {
        g_print ("Hi there :)\n");
    }
};

... et j'utilise std::bind pour le faire ressembler à une fonction non membre:

oops o;
std::function<void(GtkWidget*, GdkEvent*, gpointer)> f = std::bind(&oops::do_it, o);

mais cela ne fonctionne pas, voici le message d'erreur du compilateur:

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

Je dois résoudre ce problème en utilisant 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);

Pourquoi cela ne fonctionne-t-il pas sans spécifier std::placeholders?

16
M. Sadeq H. E.

std::bind() est conçu pour créer une entité appelable qui représente un appel (partiel) à une fonction. Cela fonctionne en liant certains paramètres de l'appel à l'objet d'appel généré, et en laissant le reste des paramètres spécifiés au moment de l'appel:

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

Le premier paramètre de std::bind() est la fonction à appeler et les autres sont les arguments de l'appel.

Dans cet exemple, l'objet call est généré avec les trois paramètres spécifiés. Par conséquent, le point d'appel n'a pas de paramètre. Considérons maintenant un appel partiel défini:

std::function<void(int,int,int)> f_call = std::bind( f );

Cela ne compile pas, car la fonction a trois paramètres et que vous n'avez spécifié personne! Cela n'a pas de sens, non? Si vous avez une fonction avec trois paramètres, vous devez transmettre trois paramètres à l'objet d'appel.

Si vous devez spécifier que certains paramètres doivent être spécifiés au point d’appel, vous devez utiliser des espaces réservés pour les représenter. Par exemple:

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)

Comme vous pouvez le constater, nous avons utilisé des espaces réservés pour spécifier trois "espaces" pour l'appel de fonction, c'est-à-dire trois paramètres qui seraient spécifiés au point d'appel.
Notez que les numéros des espaces réservés spécifient le numéro du paramètre au point d'appel . Le premier paramètre du point d’appel est identifié par _1, le second par _2, etc. Cela peut être utilisé pour spécifier des paramètres de différentes manières, en réorganisant les paramètres d'un appel de fonction, etc. Par exemple:

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

Enfin, std::bind() pourrait être utilisé pour lier une fonction membre à l'objet utilisé pour l'appeler:

struct foo
{
    void f() const;
};

int main()
{
    foo myfoo;
    std::function<void()> f = std::bind( &foo::f , std::cref( myfoo ) );

    f(); //Tah dah!
}

Une fonction membre peut être vue comme une fonction avec un paramètre caché, qui est l'objet avec lequel l'appel est fait. C'est pourquoi l'objet est lié en tant que premier paramètre.

Mais, exactement comme dans les exemples ci-dessus, si vous ne connaissez qu'un certain nombre de paramètres au niveau du point de rattachement et devez en spécifier d'autres ultérieurement, vous devez utiliser des espaces réservés :

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

Quelques détails

La signature de l'objet d'appel

Notez que nous utilisons std::function pour stocker l'objet d'appel. La signature de cette fonction dépend du type de l'objet appel généré, c'est-à-dire dépend de la façon dont vous avez spécifié les paramètres au point de rattachement .

Un objet d'appel est simplement une autre entité appelable qui joue le rôle d'appel de la fonction d'origine. Après avec nos exemples de fonctions f():

std::function<void()> f_call = std:bind( f , 1 , 2 , 3 );

Ici, la signature de l'objet d'appel est void(), car nous avons spécifié le jeu de paramètres de trous au point de rattachement et il ne reste plus personne à spécifier au point d'appel (l'objet d'appel n'a donc pas de paramètre).

En cas d'appel partiel:

std::function<void(int,int,int)> f_call = std::bind( f, _1 , _2 , _3 );

f_call( 1 , 2 , 3 );

la signature de l'objet d'appel est void(int,int,int), car nous avons laissé trois paramètres à spécifier au point d'appel (notez les espaces réservés). En général l'objet d'appel a le même nombre de paramètres que les espaces réservés que vous avez spécifiés au point de rattachement.

44
Manu343726

Notez que les numéros des espaces réservés spécifient le numéro du paramètre au niveau du point d'appel. Le premier paramètre du point d'appel est identifié par _1, le second par _2, etc.

Je veux ajouter quelques exemples pour @ Manu343726. Si vous essayez de transformer une fonction à 3 paramètres en une fonction à 2 paramètres. Vous pouvez spécifier 1 paramètre au moment de la liaison et fournir le reste 2 au moment de l'appel.

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)

maintenant 11 est x1 de add3, _1 est x2 de add3, _2 est x3 de add3. _ 2 signifie que fn a 2 paramètres à spécifier lors de l'appel. Et ce format est incorrect:

auto fn = std::bind(add3, 11, std::placeholders::_2, std::placeholders::_3);

0
robert