Rokiのチラ裏

学生による学習のログ

初期化lambdaキャプチャー

こんなものがあったのか。知らなかったのでメモ。 まず、初期化lambdaキャプチャーにより、以下のように書ける。

#include<functional>

int main()
{
    typedef std::function<int()> function_i;
    int data{};

    function_i f=[i=data]{return i;},g=[data=data]{return data;},
           h=[&i=data]{return i;},j=[&data=data]{return data;};
}

キャプチャ部分ではキャプチャ元の変数と同じ名で定義したり変更した上でキャプチャする事ができる。これが実装された理由は以下。

理由その1

#include<functional>

struct A{
private:
    unsigned int i;
public:
    A():i(0){}
    std::function<unsigned int()> lambda(){return [=](){return i;};}
};

int main()
{
    std::function<unsigned int()> f;

    {
        A a;
        f=a.lambda();
    }

    f();
}

privateゾーン内の変数をpublic関数によって引き出すというこのあまりにも無意味なコードの意味は取り敢えず置いておいて、このコードはf()を呼び出した時点で未定義の動作を引き起こす可能性がある。
何故ならば、そもそもラムダ式はクラスのデータメンバーをキャプチャする事ができない。一見、データメンバをキャプチャし、動いているように見えても、実際の動きは以下のコードと同義となる。

struct A{
private:
    unsigned int i;
public:
    A():i(0){}
    std::function<unsigned int()> lambda(){return [=](){return this->i;};} // thisをキャプチャしデータメンバーはキャプチャしない。this経由でのアクセス。
};

そして、Aのオブジェクトであるaはローカルスコープ内でしか生きていないため、this経由でデータメンバへアクセスする式を内包したf()を呼び出すと、死んだオブジェクトのthisを経由する事になり、未定義の動作となる。 これを動かすには

struct A{
private:
    unsigned int i;
public:
    A():i(0){}
    std::function<unsigned int()> lambda()
    {
        auto a=this->i;
        return [=](){return a;};
    }
};

とするしかない。this経由でなくすために、一度データを取るという、なんとも冗長な記述をしなければならない。

理由その2

C++11までのlambda式はムーブキャプチャーをするシンタックスがなかった。

#include<vector>
#include<map>
#define Heavy 1000

auto g()
{
    std::vector<int> v(Heavy);
    return [=]{return std::make_pair(std::begin(v),std::end(v));};
}

int main()
{
    auto f = g();  
}

ラムダ式クロージャーオブジェクトをmainに返さなければならないため、コピーキャプチャをしなければならないが、不要なコピー元vが、クロージャオブジェクトにコピーされたものと連立したままなため最適化したい。

解決策:初期化lambdaキャプチャー

これらの問題はすべて初期化lambdaキャプチャーにより解決する。

#include<vector>
#include<map>
#include<functional>
#define Heavy 1000

struct A{
private:
    int i;
public:
    std::function<int()> lambda(){return [i=i]{return i;};} // copy capture.not use this pointer
};

auto g()
{
    std::vector<int> v(Heavy);
    return [v=std::move(v)]{return std::make_pair(std::begin(v),std::end(v));}; // move copy captured
}

int main()
{
    std::function<int()> f;
    {
        A a;
        f=a.lambda();
    }
    f(); // Ok because function f does not use this pointer
    
    auto h = g();  
}

便利で、楽しい。実にコードの幅を広げる画期的な実装だと思う。