web-dev-qa-db-ja.com

この例(メンバー関数)でプレースホルダーがないと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を指定しないと機能しないのはなぜですか?

17
M. Sadeq H. E.

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()の最初のパラメーターは呼び出される関数であり、残りは呼び出しの引数です。

この例では、3つのパラメーターをすべて指定して呼び出しオブジェクトが生成されるため、呼び出しポイントにはパラメーターがありません。次に、部分的に定義された呼び出しを考えます。

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

関数には3つのパラメーターがあり、誰も指定していないため、これはコンパイルされません!意味がありませんよね? 3つのパラメーターを持つ関数がある場合は、3つのパラメーターを呼び出しオブジェクトに渡す必要があります。

一部のパラメーターを呼び出しポイントで指定する必要があることを指定する必要がある場合は、プレースホルダーを使用してそのパラメーターを表す必要があります。例えば:

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

ご覧のとおり、プレースホルダーを使用して、関数呼び出しの3つの「スペース」を指定しました。つまり、呼び出しポイントで指定される3つのパラメーターです。
プレースホルダーの数は呼び出しポイントでのパラメーターの数を指定することに注意してください。呼び出しポイントの最初のパラメーターは__1_で識別され、2番目のパラメーターは__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!
}
_

メンバー関数は、1つの非表示パラメーターを持つ関数と見なすことができます。これは、呼び出しが行われるオブジェクトです。そのため、オブジェクトは最初のパラメーターとしてバインドされます。

ただし、上記の例とまったく同じですバインディングポイントで特定の数のパラメータしかわかっておらず、後でコールポイントで他のパラメータを指定する必要がある場合は、プレースホルダを使用する必要があります

_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_を使用して呼び出しオブジェクトを格納することに注意してください。その関数のシグネチャは、生成される呼び出しオブジェクトのタイプによって異なります。つまり、バインドポイントでパラメーターを指定した方法によって異なりますです。

呼び出しオブジェクトは、元の関数の呼び出しとして機能する、もう1つの呼び出し可能なエンティティです。次に、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)です。これは、コールポイントで指定する3つのパラメーターを残しているためです(プレースホルダーに注意してください)。一般に呼び出しオブジェクトには、バインドポイントで指定したプレースホルダーと同じ数のパラメーターがあります。

49
Manu343726

プレースホルダーの数は、呼び出しポイントでのパラメーターの数を指定することに注意してください。呼び出しポイントの最初のパラメーターは_1で識別され、2番目のパラメーターは_2で識別されます。

@ Manu343726の例をいくつか追加したいと思います。 3つのパラメーターを持つ関数を2つのパラメーターを持つ関数に変換しようとした場合。バインド時に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はx3のadd3、_1はx2のadd3、_2はx3のadd3です。 _2は、fnに、呼び出し時に指定する2つのパラメーターがあることを意味します。そして、このフォーマットは間違っています:

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

0
robert