ラムダオブジェクトを static 変数で保持する時の注意

Lingr の C++ 部屋で話題になったのですが、参照キャプチャしたラムダを static 変数で保持するのは未定義の動作になることがあるので注意しましょうという話。

さて、話題になっていたのは次のようなコードです。

void func(int const& i){
    static auto f = [&]{ std::cout << i << std::endl; };
    f();
}

func(1); // => 1
func(2); // => 1


呼び出した f() は両方共 1 が出力されます。
ここで引数を int に変えてみるとどうでしょう。

// 引数を int に変更
void func(int i){
    static auto f = [&]{ std::cout << i << std::endl; };
    f();
}

// 最初のコードと結果が違う
func(1); // => 1
func(2); // => 2


と、いうように最初のコードと結果が異なります。
え、なにこれ。
(ちなみに clang, gcc(多分 msvc も)ともに同様の結果です。


そもそもコードをよく見てみると参照キャプチャしたラムダを static 変数で保持しています。
この参照キャプチャしている部分が問題で、関数を抜けるときにキャプチャしている変数のオブジェクトは破棄されます。
しかし、static 変数でラムダを保持しているため、2回目以降もその破棄されたオブジェクトを参照するため、dangling reference となり未定義動作になります。
詳しくはここら辺を参照してください。


このため、上記のようにキャプチャしたラムダを static 変数で保持したいのであればコピーキャプチャする必要があります。

void func(int i){
    // コピーキャプチャする
    static auto f = [=]{ std::cout << i << std::endl; };
    f();
}

func(1); // => 1
func(2); // => 1


これなら意図した動作になると思います。