日別アーカイブ: 2009年5月25日

VisualStudio 2010 beta lambda 式

VisualC++ 2010 beta ではラムダ式が使えます。
無名の関数を inline で宣言することが可能で、関数オブジェクトのように
取り扱いできます。

Download Visual Studio 2010 Beta
Examples of Lambda Expressions

変数で受け取る場合は auto や template を使った汎用の入れ物が必要です。

auto f0= []( int x, int y ){ return x * y; };
std::tr1::function f1= f0;

lambda 式は無名の型宣言に相当するので、同等の型を明示的に宣言することが
できません。
名前をつけるだけなら下記の定義は出来ますが、これも f0 が有効な範囲のみです。

typedef decltype(f0) lambda_type2;
lambda_type2 f2= f0;

全く中身が同じ lambda 式を定義しても別の型と見なされるようです。

auto	f2= []( int a, int b ){ return	a * b; };
auto	f3= []( int a, int b ){ return	a * b; };
decltype(f2)*	fp2= &f3;  // error

上記の例はエラーです。
そのため定義内容を知らない外部から直接参照する手段がありません。

実際に使ってみると単純な関数ポインタでは無いことがわかります。

・lambda 式に応じて無名のクロージャー用クラスが定義される
・メンバとしてキャプチャした変数のコピーまたは参照が格納される
・クロージャークラスのメンバ関数として呼び出される。

確認してみます。

int v= 0;
auto  f4= []( int a, int b ){ return  a * b; };
auto  f5= [&]( int a, int b ){ return  a * b + v; };
auto  f6= [=]( int a, int b ){ return  a * b + v; };

sizeof(f4) == 1
sizeof(f5) == 8 // x64
sizeof(f6) == 4

sizeof(f4) が 1 なのは実質的にサイズが無いことを意味しています。
sizeof(f5) と sizeof(f6) はどちらも v を参照していますが、たまたま x64 で
コンパイルしていたので異なるサイズになりました。
ポインタ(参照)である証拠で x86 だと 4 になります。

auto	f4= []( int a, int b ){ return a * b; };
f4.operator()( 2, 10 );

operator() の呼び出しが出来ます。

int	v;
auto	f5= [&]( int a, int b ){ return a * b + v; };
int	r0= f5.v; // cannot access private member
int	r1= f5.w; // is not a member

直接アクセスはエラー。変数名はそのままメンバになってます。

lambda 式の定義が返す値はその場でキャプチャされたクロージャーそのものであるということ。
クロージャー未使用でも空の構造体が定義されること。
実行する関数はクロージャー型と静的に関連づけられており、そのために型は常にユニークとなること。
lambda 式の呼び出しは静的なものであってポインタでは無いということ
等がわかります。

実際に出力コードも追いかけて確認。

きちんとインライン展開されているし、これで定義を知らない外部からは直接アクセス
できない理由もわかりました。
外部関数呼び出しのようにインライン出来ない場合は std::tr1::function のような
関数オブジェクトが必要になるわけです。

struct func_base_2 {
    virtual int  operator()( int a, int b )= 0;
};

template
struct func_impl_2 : public func_base_2 {
    T  exf;
    func_impl_2( T lambda_f ) : exf( lambda_f ) {}
    int	operator()( int a, int b )
    {
        return  exf( a, b );
    }
};

struct func_2 {
    func_base_2*   ptr;
    template
    func_2( const T&& lambda_f )
    {
        ptr= new func_impl_2( lambda_f );
    }
    template
    func_2( T& lambda_f )
    {
        ptr= new func_impl_2( lambda_f );
    }
    int	operator()( int a, int b )
    {
        return	(*ptr)( a, b );
    }
    ~
};


    func_2    f7( []( int x, int y ){ return x*x + y*y; } );
    int   mm= f7( 3, 9 );

この場合の f7 は callback として持ち出せます。
使えるのはクロージャーが作られた関数スコープの中のみです。

関連エントリ
VisualStudio 2010 beta1