Cでよくある非同期関数のコールバック関数でラムダ導入子有りのラムダ式を使えるようにしたい（続き）

## ことの発端

Cでよくある非同期関数

```cpp
extern "C" int asyncfunc(int p1, int p2, void* refcon, void(*callback)(void*, int));
```

```cpp:動く
asyncfunc(1, 2, nullptr, [](void*, int resp){
   const int n = 10;
   std::cout << resp * n << std::endl;
});
```

```cpp:コンパイルエラー
const int n = 10;
asyncfunc(1, 2, nullptr, [=](void*, int resp){
   std::cout << resp * n << std::endl;
});
```

ここに、ラムダ導入子有りのラムダ式を使うとエラーになるという問題を何とかできないかという話です。
これは、前回投稿させていただいた「[Cでよくある非同期関数のコールバック関数でラムダ導入子有りのラムダ式を使えるようにしたい](https://qiita.com/terukazu/items/05ba5898e692d3173a61)」の続きになります。

## 条件的なやつ

* 非同期関数は必ずコールバック関数を呼び出す（中断するとコールバック関数を呼ばないとかナシ）
* ラムダ式のキャプチャ並びはコピーのみとする（参照にすると生存期間がややこしい）
* 非同期関数は refcon を受け取りコールバック関数にパスしてくれる
* 非同期関数の最終引数はコールバック関数[^1]
* 非同期関数の最終引数の１個前は refcon[^1]

[^1]: 今回追加した条件になります。

## 書いてみた

で、前回投稿させていただいた ```staticify``` を使って無い知恵絞ってみた感じです。

```cpp
#include <iostream>
#include <tuple>
#include <functional>
#include <thread>
#include <sstream>

extern "C" void asyncfunc(int p1, int p2, void* refcon, void(*callback)(void*, int)) {
  /* std::thread がメモリリークしますが、テストコードなので勘弁してください */
  new std::thread([=](){
    std::this_thread::sleep_for(std::chrono::seconds(2));
    callback(refcon, p1 + p2);
  });
}

template <int N, typename FUNCTION> class staticify;

template <int N, typename RESULT, typename ...ARGS>
  class staticify<N, RESULT (*) (ARGS...)>
{
public:
  using F_RES   = RESULT;
  using F_ARGS  = std::tuple<ARGS...>;
  using F_FUNC  = std::function<RESULT(ARGS...)>;
  using F_TYPE  = RESULT(ARGS...);

private:
  struct wrap_t {
    F_FUNC f;
  };

  F_FUNC  m_func;

  static F_RES sfunc(ARGS ...args){
    auto params = std::tuple<ARGS...>(args...);
    auto w = reinterpret_cast<wrap_t*>(std::get<N>(params));
    auto f = w->f;
    delete w;
    return f(args...);
  } 

public:
  staticify(F_FUNC f): m_func(f) {}
  void* refcon() { return new wrap_t{ .f = m_func }; }
  static F_TYPE* callback() { return &sfunc; }
};

template <int N, typename FUNCTION> class lambda_enabler;

template <int N, typename RESULT, typename ...ARGS>
  class lambda_enabler<N, RESULT (ARGS...)>
{
  using F_RES       = RESULT;
  using F_ARGS      = std::tuple<ARGS...>;
  using F_FUNC      = std::function<RESULT(ARGS...)>;
  using CB_F_TYPE   = typename std::tuple_element<std::tuple_size<F_ARGS>::value - 1, F_ARGS>::type;
  using STATICIFY   = staticify<N, CB_F_TYPE>;
  using CB_FUNC     = std::function<typename STATICIFY::F_TYPE>;

  F_FUNC m_target;

public:
  lambda_enabler(F_FUNC target) : m_target(target) {}
  template <typename ...LARGS> auto prepare(LARGS ...largs) {
    struct p {
      const std::function <F_RES(void*, CB_F_TYPE)> f;
      void call(CB_FUNC cb) {
        STATICIFY x(cb);
        f(x.refcon(), x.callback());
      }
    };
    return p{ .f = std::bind(m_target, largs..., std::placeholders::_1, std::placeholders::_2)};
  }
};

int main(void) {
  auto x = lambda_enabler<0, decltype(asyncfunc)>(asyncfunc);

  for(auto i = 0; i < 10; i++){
    x.prepare(i + 1, i + 3).call([=](auto, auto n){
      std::stringstream ss;
      ss << n * i << std::endl;
      std::cout << ss.str();
    });
  }

 std::this_thread::sleep_for(std::chrono::seconds(5));
}
```

```lambda_enable``` という名前が微妙なのは勘弁していただいて。。。

## 使い方

例えば ```asyncfunc``` で、やりたいことが、こんな感じだとする。

```cpp:これをやりたいがエラーになる
int i = 100;
asyncfunc(1, 2, nullptr, [=](auto, auto n){
  std::cout << i + n;
});
```

で、```lambda_enabler``` をこんな感じで生成します。

```cpp
auto x = lambda_enabler<0, decltype(asyncfunc)>(asyncfunc);
```

テンプレートパラメータの先頭にある```0```（非型テンプレートパラメータ）はコールバック関数で refcon が何個目のパラメータに入るかという情報になります。

以降は、こんな感じで使えます。

```cpp
int i = 100;
x.prepare(1, 2).call([=](/* void* */ auto, /* int */ auto n){
  std::cout << i + n;
});
```

ラムダ式で記述しているコールバック関数の第１引数はrefconが渡ってきますが、決して触らないでください。（苦笑）

```prepare()``` で```refcon```と```コールバック関数```以外を指定して、そいつからの ```call()``` で実際に ```asyncfunc``` を呼び出すみたいな感じです。

## 振り返り

* 何となく良さそうな気がしないこともない。
* 非同期関数のrefconを渡す場所が最終引数の１個前に固定されるのが残念。（でも、こんな引数のパターンが多いんじゃない？）
* パラメータパックの最後の２つを差し替えたいんだけど方法が思いつかなかったので ```prepare``` の型が不定になってしまう。（勿論、型が違えばコンパイル時にエラーになりますが）[^2]
* 稀に（よくある？）、```asyncfunc``` の返却値を確認しなきゃならない場合があったりするが、それは無視する方向で良いケースと悪いケースがあるので、その辺の機能を入れても良いが複雑になるなぁ〜
* ラムダ式にrefcon無くしたいかも

[^2]: トライしてみました → [可変引数テンプレートのパラメータパックを任意の場所で分解して std::tuple にしたい](https://qiita.com/terukazu/items/f3218b613b13dcd0fdc1) 

## github で公開しました（2021/03/06 追記）

要素機能的なものを組み合わせ、コードを整理して github に公開してみました。

https://github.com/codianz/asyncfn_util

ついでに、C++17 以降であれば、```operator()```をオーバーロードして、こんな感じで呼び出せるようにしてみました。

```cpp
asyncfn({1, 2}, [=]((/* void* */ auto, /* int */ auto n){
  std::cout << i + n;
});
```

これで、かなり快適になりました。
ただ、C++17使えば```lambda_enabler```を実体化する際に ```decltype(asyncfunc)``` を推論でいけるかと思ったのですが、ダメでした。。。
まだ修業が足りない。。。

## 実は（2021/03/06 追記）

node.js の ```util.promisify``` を知り、C++ でも出来ないかな～というのが、本当の「ことの発端」でした。
で、これまでの積み重ねでようやく非同期関数を reactive extension の ```observable``` に変換する ```observablify``` を作ることができました。[^3]

[^3]: 実は先日```util.promisify```をC++でやる的な投稿したのですが、非同期関数のコールバック関数を関数ポインタではなく```std::function``` で受けてしまい、普通にラムダ式が使えてしまえるということに気づき投稿は削除、そして、そこからこんな展開になるとは。。。

```cpp
auto rxfn = observablify<0, decltype(asyncfunc)>(asyncfunc);
rxcpp::observable<>::range(0, 9, rxcpp::observe_on_new_thread())
.flat_map([=](auto n){
  return rxfn.rx(n + 1, n + 3)
  .map([=](auto r){
    return std::get<1>(r) * n;
  }).as_dynamic();
}).as_dynamic()
.subscribe([](auto x){
  std::cout << x << std::endl;
});
```

じゃ～、```rxjs``` の ```observable``` もと思ったけど、 ```util.promisify```があるなら```Observable.from```でしまいですね。（苦笑）

メタプログラミングって、ウチらみたいな業務アプリ作っているとなかなか作る機会がないけど、ひょんなキッカケで体験することができて色々勉強になりました。