Почему std:: bind не работает без заполнителей в этом примере (функция-член)?

Например, это моя функция-член (do_it):

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

... и я использую std::bind, чтобы он выглядел как функция, не являющаяся членом:

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

но это не работает, следующее сообщение об ошибке компилятора:

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

Мне нужно исправить это, используя 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);

Почему это не работает без указания std::placeholders?

Ответы

Ответ 1

std::bind() предназначен для создания вызываемого объекта, который представляет (частичный) вызов функции. Он работает с привязкой некоторых параметров вызова к генерируемому объекту вызова и позволяет описать остальные параметры в точке вызова:

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

Первым параметром std::bind() является вызываемая функция, а остальные - аргументы вызова.

В этом примере объект вызова генерируется со всеми указанными тремя параметрами, поэтому точка вызова не имеет параметров. Теперь рассмотрим частично определенный вызов:

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

Это не скомпилируется, потому что функция имеет три параметра, и вы никого не указали! Это не имеет смысла, правильно? Если у вас есть функция с тремя параметрами, вы должны передать три параметра объекту вызова.

Если вам нужно указать, что некоторые параметры должны быть указаны в точке вызова, вы должны использовать заполнители для представления этих параметров. Например:

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)

Как вы можете видеть, мы использовали заполнители, чтобы указать три "пробела" для вызова функции, т.е. три параметра, которые будут указаны в точке вызова.
Обратите внимание, что числа заполнителей указывают номер параметра в точке вызова. Первый параметр точки вызова идентифицируется _1, второй - _2 и т.д. Это можно использовать для указания параметров по-разному, переупорядочения параметров вызова функции и т.д. Например:

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

Наконец, std::bind() можно использовать для привязки функции-члена к объекту, используемому для его вызова:

struct foo
{
    void f() const;
};

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

    f(); //Tah dah!
}

Функция-член может рассматриваться как функция с одним скрытым параметром, который является объектом, с которым выполняется вызов. Вот почему объект привязан как первый параметр.

Но, как и в приведенных выше примерах, , если вы знаете только определенное количество параметров в точке привязки и должны указывать другие позже в точке вызова, вы должны использовать заполнители:

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

Некоторые сведения

Подпись объекта вызова

Обратите внимание, что мы используем std::function для хранения объекта вызова. Подпись этой функции зависит от типа генерируемого объекта вызова, то есть зависит от того, как вы указали параметры в точке привязки.

Объект вызова - это еще один вызываемый объект, который действует как вызов исходной функции. Следуя нашим f() примерам функций:

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

Здесь подпись объекта вызова void(), потому что мы указали набор отверстий параметров в точке привязки, и никто не должен быть указан в точке вызова (поэтому объект вызова не имеет параметров).

В случае частичного вызова:

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

f_call( 1 , 2 , 3 );

подпись объекта вызова void(int,int,int), потому что мы оставили три параметра, которые должны быть указаны в точке вызова (обратите внимание на заполнители). В общем случае объект вызова имеет такое же количество параметров, что и заполнители, которые вы указали в точке привязки..

Ответ 2

Обратите внимание, что номера заполнителей задают номер параметра в точке вызова. Первый параметр точки вызова идентифицируется _1, второй - _2 и т.д.

Я хочу добавить несколько примеров для @Manu343726. Если вы попытаетесь преобразовать функцию с 3 параметрами в функцию с двумя параметрами. Вы можете указать 1 параметр во время привязки и предоставить остальные 2 во время разговора.

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)

теперь 11 является x1 из add3, _1 является x2 из add3, _2 является x3 из add3. _2 означает, что fn имеет 2 параметра для указания во время вызова. И этот формат неверен:

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