ラムダ式のキャプチャで値のコピーを減らしたい

## ことの発端

`C++` で関数型プログラミングをやっているとラムダ式は避けて通れないのですが、外部変数をキャプチャする際に、どうすれば無駄なコピーを減らせるかということで、プログラムを書いて検証しました。
勿論、参照が一番効率が良いのですが、非同期プログラミングと関数型プログラミングが合体すると参照の使用は `ダングリングポインタ` の温床になりますので、参照でのキャプチャは細心の注意が必要です。


## 検証プログラム

```cpp
#include <iostream>
using namespace std;

struct A {
    A()             { cout << "A::ctor default" << endl; }
    A(const A&)     { cout << "A::ctor copy" << endl; }
    A(A&&)          { cout << "A::ctor move" << endl; }
    ~A()            { cout << "A::dtor" << endl; }
    void f() const  { cout << "A::f" << endl; }
};

template <typename T> void baz(T&& x) {
    cout << "baz()" << endl;
    auto xx = std::move(x);　
    xx.f();
}

template <typename T> void bar(T&& x) {
    cout << "bar()" << endl;
    baz(std::forward<T>(x));
}

template <typename T> auto foo(T&& x) {
    cout << "foo()" << endl;
    return [???](){ // <- ここのキャプチャをどうするかという問題です
        bar(x);
    };
}

int main(void){
    {
        auto f1 = []{
            cout << "#1" << endl;
            const A a;
            return foo(a);
        }();

        f1();
    }
    {
        auto f2 = []{
            cout << "#2" << endl;
            A a;
            return foo(a);
        }();
        f2();
    }
    {
        auto f3 = []{
            cout << "#3" << endl;
            A a;
            return foo(std::move(a));
        }();
        f3();
    }
}
```

やりたきことは、`A` というオブジェクトを `foo()` -> `bar()` -> `baz()` に転送するということです。そして、`bar()` は `foo()` で定義するラムダ式で呼び出されているので、`A` をどうやって転送すると安全に `A` のコピーを少なくできるかという問題です。
更にややこしいのですが、`main()` で `f1` `f2` `f3` を定義していますが、これは`関数を返す関数`です。（`foo()`も関数を返却する関数です）つまり、`f1` `f2` `f3` を実行する際には`foo()` を呼び出す手前で生成された `A` は破棄されていることになります。

## 参照でキャプチャ

```cpp
template <typename T> auto foo(T&& x) {
    cout << "foo()" << endl;
    return [&x](){
        bar(std::forward<T>(x));
    };
}
```

```:結果
#1
A::ctor default
foo()
A::dtor
#1 call
bar()
baz()
A::ctor copy <- 破棄した A をコピーしている！
A::foo
A::dtor
#2
A::ctor default
foo()
A::dtor
#2 call
bar()
baz()
A::ctor move <- 破棄した A をムーブしている！
A::foo
A::dtor
#3
A::ctor default
foo()
A::dtor
#3 call
bar()
baz()
A::ctor move <- 破棄した A をムーブしている！
A::foo
A::dtor
```

今回のケースでは `baz()` まで `A` が転送されてる感じですが、 `A` のインスタンスは既に破棄されているため、`A::ctor copy` や `A::ctor move` では壮絶な事態になっています。冒頭で触れたダングリングポインタを触っていることになります。この検証プログラムでは落ちませんが、一番ダメなパターンです。

しかしながら、ダングリングポインタが発生しないケースにおいては最適かと思います。（`#2`の`baz()`内が `move`されちゃうのがちょっと悩ましい感じではありますが）


## コピーでキャプチャ

そこで、安心安全なコピーを使います。

```cpp
template <typename T> auto foo(T&& x) {
    cout << "foo()" << endl;
    return [x](){
        bar(std::move(x));
    };
}
```

```:結果
#1
A::ctor default
foo()
A::ctor copy
A::dtor
#1 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
#2
A::ctor default
foo()
A::ctor copy
A::dtor
#2 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
#3
A::ctor default
foo()
A::ctor copy <- ムーブできるのにコピーになっている
A::dtor
#3 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
```

ダングリングポインタは発生していないのですが、`move` できるのに `copy` しているので、これを何とかしたいのですが `C++11` ではこれが限界。
`<- ツッコミどころ` がありますが、後で回収するので、ここではスルーします。


## `std::move` でキャプチャ

`C++14` からは[初期化キャプチャ(init-capture)](https://cpprefjp.github.io/lang/cpp14/initialize_capture.html)が登場したので、キャプチャする変数を宣言する際に初期化することができるようになりました。
そこで、キャプチャ指定をする箇所で `move` してみます。

```cpp
template <typename T> auto foo(T&& x) {
    cout << "foo()" << endl;
    return [x = std::move(x)](){
        bar(std::move(x));
    };
}
```

```:結果
#1
A::ctor default
foo()
A::ctor copy
A::dtor
#1 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
#2
A::ctor default
foo()
A::ctor move <- これでいいのか？
A::dtor
#2 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
#3
A::ctor default
foo()
A::ctor move <- 良い感じ！
A::dtor
#3 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
```

これはこれで良いのですが、気になる点として `#2` でムーブされるのが微妙。これだと `foo()` が引数を破壊するかを知っていないと予期しない問題が発生するかも知れません。
やはり、ここは、右辺値ならムーブ、それ以外はコピーとしたいところです。
と、まぁ、ここは `初期化キャプチャ` の問題では無いですけどね。


## `std::forward` でキャプチャ

```cpp
template <typename T> auto foo(T&& x) {
    cout << "foo()" << endl;
    return [x = std::forward<T>(x)](){
        bar(std::move(x));
    };
}
```

```:結果
#1
A::ctor default
foo()
A::ctor copy
A::dtor
#1 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
#2
A::ctor default
foo()
A::ctor copy
A::dtor
#2 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
#3
A::ctor default
foo()
A::ctor move
A::dtor
#3 call
bar()
baz()
A::ctor copy <- ツッコミどころ
A::foo
A::dtor
A::dtor
```

これで良い感じになった気がしますが、どうにも `ツッコミどころ` が気になります。

## ツッコミどころ

最後に伏線を回収します。

`結果` の `<- ツッコミどころ` にあるように `baz()` が全てコピーになっている点が気に入りません。
これはラムダ式でキャプチャした変数そのものには `const` 属性は付かないのですが、呼び出される関数が`const`属性のため、キャプチャした変数を変更することができません。
そこでラムダ式に `mutable` 属性を付与することで、キャプチャした変数を`move`することができます。[^1]

[^1]: ラムダ式は関数オブジェクトなので、キャプチャした変数は関数オブジェクトのメンバ変数となります。

```cpp
template <typename T> auto foo(T&& x) {
    cout << "foo()" << endl;
    return [x = std::forward<T>(x)]() mutable {
        bar(std::move(x));
    };
}
```

```結果
#1
A::ctor default
foo()
A::ctor copy
A::dtor
#1 call
bar()
baz()
A::ctor move
A::foo
A::dtor
A::dtor
#2
A::ctor default
foo()
A::ctor copy
A::dtor
#2 call
bar()
baz()
A::ctor move
A::foo
A::dtor
A::dtor
#3
A::ctor default
foo()
A::ctor move
A::dtor
#3 call
bar()
baz()
A::ctor move
A::foo
A::dtor
A::dtor
```

これで、全てが丸く収まったんじゃないかと。


## 所感

最初の参照をキャプチャするケース以外は「動作はする」のが厄介だなと思います。
まぁ、キャプチャする変数がプリミティブ型なら良いですが、巨大なオブジェクトになると、値の転送方法はパフォーマンスに大きな影響を与えるため、取り扱いには注意が必要ですね。

ちなみに、初期化キャプチャでの　`template` の `パラメータパック` の展開は `C++20` までオアズケです。（`std::tuple`を使った無理矢理な感じの手法がありますが、`tuple` から`パラメータパック`に戻すために `std::apply` が必要になり、 `std::apply` は `C++17` 以降で使用可能です。まぁ、自作すれば良いといえば良いけど。。。）

```cpp:C++20
template <typename...ARGS> auto f(ARGS&&...args) {
  return [...args = std::forward<ARGS>(args)]{
    x(std::move(args)...);
  }
}
```

```cpp:C++17
template <typename...ARGS> auto f(ARGS&&...args) {
  return [tpl = std::make_tuple(std::forward<ARGS>(args)...)]{
    std::apply([](auto&&...args){
      x(std::move(args)...);
    }, tpl);
  }
}
```

あ、あと案外見落としがち[^2]なのが、関数オブジェクトそのもののコピーです。これも内包している変数をコピーしますので、注意が必要です。

[^2]: 「見落としがち」な人は自分です。

```cpp
template <typename F> void func(F f) {
  f();
}

int main() {
  A a;
  auto f = [a = std::move(a)](){
    a.f();
  };
  func(f);
}
```

```結果
A::ctor default
A::ctor move
A::ctor copy
A::f
A::dtor
A::dtor
A::dtor
```