keyword:

  • std::function
  • lamda expression
  • putting the return value after the function
  • function object (= a closure)

参考

 

基本

ラムダキャプチャ

[&] : capture all external variable by reference
[=] : capture all external variable by value
[a, &b] : capture a by value and b by reference

        int a = 4;
        int b = 5;
        auto getValue3 = [&b]()->int
        {
            return b + a;
        };
        // "a is not captured" のエラーが出る。
        // キャプチャを [&], [=], [&b, a], [&b, &a] にしたら a をキャプチャできる。
        std::cout << getValue3() << std::endl;

ユースケース

ラムダ式を渡して挙動を変える – std::count_if()

        std::array<int, 4> vars{ 1, 2, 3, 4 };
        auto count = std::count_if( vars.begin(), vars.end(),
            []( int var ){ return var > 2; } );
        std::cout << "Count: " << count << std::endl;

ラムダ式をテンプレート引数で渡す

// ラムダ式を引数で受け渡す。柔軟性を持たせるために template parameter としている。
// 型名を書くのが面倒という理由もある。std::function<void()>& を使用しても良い。
template<typename TAllocator>
void* Allocate( size_t size, TAllocator&& Allocator )
{
return Allocator( size );
}

void* LargeSlabAllocator( size_t size )
{
return malloc( size );
}

void* SmallSlabAllocator( size_t size )
{
return malloc( size );
}

int main()
{
    // pass lamda expression through template parameter
    {
        auto p = Allocate( 4, []( size_t size ){return LargeSlabAllocator( size ); } );
        p = Allocate( 4, []( size_t size ){return SmallSlabAllocator( size ); } );
    }
}

実際の使いどころは

 

 

 

その他

ラムダの受け取りに右辺値参照を使用するのはなぜか?

&&を使うかどうかはラムダがtrivially copyableかどうかに依存する。しかし、実際にはラムダのcopyとmoveで違いは無い。capture by valueが無い場合はラムダは普通はtryvially copyable、trivially moveableになる。

 

メモ

申し送り

  • 取り敢えず関数オブジェクトのインスタンスを作ってそれを渡してやればいいんじゃないか?
    関数オブジェクトのコピー、ポインタ渡しのサンプル

    typedef struct lambda
    {
    public:
    	lambda(){}
    
    	lambda( int x ) : x( x ) { }
    	int operator ()( int y ) { return x + y; }
    
    private:
    	int x;
    } lamda;
    
    template <class Functor>
    class Hoge
    {
    public:
    	// ラムダを受け取る
    	Hoge( Functor functor)
    	{
    		m_Func = functor;
    	}
    
    	Hoge( Functor* pFunctor )
    	{
    		m_pFunc = pFunctor;
    	}
    
    	int Execute()
    	{
    		return m_Func( 1 );
    	}
    
    	int ExecutePtr()
    	{
    		return ( *m_pFunc )( 1 );
    	}
    
    private:
    	Functor m_Func;
    	Functor* m_pFunc;
    };
    
    int main()
    {
    	lamda* pLamda = nullptr;
    	Hoge<lamda>* pHoge = nullptr;
    
    	// 関数オブジェクトのコピーを渡す	-> できる
    	{
    		pLamda = new lamda( 1 );
    		pHoge = new Hoge<lamda>( *pLamda );
    		int ret = pHoge->Execute();
    
    		delete pLamda;
    		delete pHoge;
    	}
    
    	// 関数オブジェクトのポインタを渡す	-> できる
    	{
    		pLamda = new lamda( 2 );
    		pHoge = new Hoge<lamda>( pLamda );
    		int ret = pHoge->ExecutePtr();
    
    		delete pLamda;
    		delete pHoge;
    	}
    }
    

     

 

メモ

  • ラムダ式はコンパイラで functor class を作っているとコンセプト的には等価
  • Callable object
  • pointer-to-callable-object
  • ラムダ式の生存期間は?
    • スコープ内で有効だけどもスコープに入る度に new される?
    • スタック領域に確保されるだけ?
    • それともコンパイル時に静的に確保されて利用範囲をスコープ内にきっているだけ?

スコープ内で有効だけどもスコープに入る度に new される?

ラムダ式と関数オブジェクトは等価

下記は等価です。

[x](int y) { return x + y; };

struct Lambda
{
    Lambda(int x): x(x) {}
    int operator()(int y) const { return x + y; }
private:
    int x;
};