decltype と ラムダ式 と 私

## ことの発端

昨日の「[template パラメータ内でラムダ式](https://qiita.com/terukazu/items/da377debbe9b0e58e199)」という記事で ```decltype``` が消化できていないので、テストコードを書いて腹落ちさせたい。

更に、```std::unique_ptr``` に副作用がある変数をキャプチャさせた際にどうなるかとコードを書いてみたら、コンパイラが激おこだったので、理由を知りたいという動機です。

```cpp
#include <iostream>
#include <memory>
int main(void)
{
  const auto deleter1 = [](char* p){ ::free(p); };
  std::unique_ptr<char, decltype(deleter1)> p1(reinterpret_cast<char*>(malloc(1)));

  bool bDelete = false;
  const auto deleter2 = [&bDelete](char* p){ if(bDelete) ::free(p); };
  std::unique_ptr<char, decltype(deleter2)> p2(reinterpret_cast<char*>(malloc(1)));
}
```

```
Main.cpp:10:45: error: no matching constructor for initialization of 'std::unique_ptr<char, decltype(deleter2)>' (aka 'unique_ptr<char, const (lambda at Main.cpp:9:25)>')
  std::unique_ptr<char, decltype(deleter2)> p2(reinterpret_cast<char*>(malloc(1)));
                                            ^  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

## テストコード１

いきなり難しい事ができないので、まずは簡単なものからやってみる。
```decltype(expr)``` がexprの型を返すのは知っているが、改めてそれを実感してみる。

```cpp
#include <iostream>
int main(void)
{
  std::string s = "ABC";
  using stype = decltype(s);
  stype ss = "DEF";
  std::cout << typeid(s).name() << std::endl;
  std::cout << typeid(s).name() << std::endl;
}
```

```:コンソール出力
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
```

ここまでは良いとして。


## テストコード２

次に本題のラムダ式で ```decltype``` を使ってみる。

```cpp
#include <iostream>
int main(void)
{
  const auto f = [](int a, int b){
      return a + b;
  };
  using ftype = decltype(f);
  ftype ff;
  std::cout << typeid(f).name() << std::endl;
  std::cout << typeid(ff).name() << std::endl;
  std::cout << typeid(decltype(f(0,0))).name() << std::endl;
  std::cout << typeid(decltype(ff(0,0))).name() << std::endl;
}
```

```:コンソール出力
Z4mainE3$_0
Z4mainE3$_0
i
i
```

マングリング化されていて分かりにくいが、ラムダ式によって ```$_0``` という型が定義[^1]され、続けて ```$_0```をインスタンス生成し、そいつが f に代入されると。
ちと、```f(0,0)``` とか ```f(0,0)``` が ```int``` という型になるのは ```delctype``` 仕様で理解はしているが、関数型が取得できても良いような気がするが、それは仕様の問題ないし別に腹落ちしない訳じゃないので良しとする。（上から目線でごめんなさい）

## テストコード３

で、コンパイラが激おこだったコードに近いのかな？
副作用を持つ関数オブジェクトの場合です。

```cpp
#include <iostream>
int main(void)
{
  auto bAdd = false;
  const auto f = [&](int a, int b){
      return bAdd ? a + b : a - b;
  };
  using ftype = decltype(f);
//  ftype ff; /* error: no matching constructor for initialization of 'ftype' */
  std::cout << typeid(f).name() << std::endl;
//  std::cout << typeid(ff).name() << std::endl;
  std::cout << typeid(decltype(f(0,0))).name() << std::endl;
}
```

```:コンソール出力
Z4mainE3$_0
i
```

```ftype ff;``` でエラーになります。
そりゃそうです。
キャプチャした変数はクラスオブジェクトのメンバ変数になる訳で、そいつを初期化して生成しないとインスタンス生成なんてできる訳ない。
そして、初期化する術を用意していない。[^2]
コンパイラのエラーは理解したが、このエラーが出るって事は、誰かが生成をしようとしているに違いない。
もう、型と値（オブジェクトインスタンス）が混在して迷子になることが多い。。。

で、「ことの発端」である ```std::unique_ptr``` のテンプレートパラメータ仮引数が値ではなく型だということを改めて理解し、```decltype``` を使う理由はここで腹落ちしました。

## テストコード４

でも、もう少しやってみます。
次は、ラムダ式の生成と破棄のタイミングを知りたくて[^3]、ラムダ式っぽいことを自作してみます。
（テストコード４では関数オブジェクト型定義まで）

```cpp
#include <iostream>
#include <memory>
int main(void)
{
  struct deleter_t {
    deleter_t() { std::cout << "deleter_t::ctor()" << std::endl; }
    ~deleter_t() { std::cout << "deleter_t::dtor()" << std::endl; }
    void operator () (char* p) const { std::cout << "deleter_t::operator()" << std::endl; ::free(p); }
  };
  
  std::cout << "scope in" << std::endl;
  {
    std::unique_ptr<char, deleter_t> p(reinterpret_cast<char*>(malloc(1)));
  }
  std::cout << "scope out" << std::endl;
}
```

```:コンソール出力
scope in
deleter_t::ctor()
deleter_t::operator()
deleter_t::dtor()
scope out
```

ここまでくると、ふむふむって感じで、ようやく気持ちが穏やかな世界に戻れました。
余談ですが、```operator () ```が```const```をいつもの癖で付加していたけど、ラムダ式でこの ```const```属性を外すのが ```mutable``` という仕様も理解しました。[^4]


## テストコード５

「テストコード４」の続きで ```decltype``` が使われて機能している感じを知りたく、さらにラムダ式に寄せるべく、クラスオブジェクトの生成を行います。

```cpp
#include <iostream>
#include <memory>
int main(void)
{
  struct deleter_t {
    deleter_t() { std::cout << "deleter_t::ctor()" << std::endl; }
    ~deleter_t() { std::cout << "deleter_t::dtor()" << std::endl; }
    void operator () (char* p) const { std::cout << "deleter_t::operator()" << std::endl; ::free(p); }
  };

  const auto deleter = deleter_t();

  /* 下記のラムダ式は概ね上記の「型定義」と「オブジェクト生成」をまとめた糖衣構文 */
  // const auto deleter = [](char* p){ ::free(p); };

  std::cout << "scope in" << std::endl;
  {
    std::unique_ptr<char, decltype(deleter)> p(reinterpret_cast<char*>(malloc(1)));
  }
  std::cout << "scope out" << std::endl;
}
```

```:コンソール出力
deleter_t::ctor()
scope in
deleter_t::ctor()
deleter_t::operator()
deleter_t::dtor()
scope out
deleter_t::dtor()
```

ここにきて、そうか、ラムダ式で得られたオブジェクトを```decltype```するんだから、無駄な生成と破棄が多く発生するのか。。。
いや、もう凡人はここまでで良いでしょう。
ようやく、これで、ゆっくり眠れそう。


## ふと

```cpp
using deleter_t = decltype([](char* p){::free(p);});
std::unique_ptr<char, decltype([](char* p){::free(p);})> p(reinterpret_cast<char*>(malloc(1)));
```
こんなこと考えて、「[template パラメータ内でラムダ式](https://qiita.com/terukazu/items/da377debbe9b0e58e199)」にループしそう。。。


[^1]: ストラウストラップ氏著「プログラミング言語C++」の「§11.4.5 ラムダ式の型」によると、ラムダ式の型は定義されず closure type と呼ばれるラムダ式特有の型になるとしている。

[^2]: どこかに明確な記述があった気がするけど、探しきれていない。

[^3]: これ、調べる術って無いのかな？ Reactive Extension 使っていると never, retry とか、 completed や、 unsibscribe し忘れで、結構問題になるんだよな。。。

[^4]: ストラウストラップ氏著「プログラミング言語C++」「§11.4.1 実装モデル」