RTTR で C++11 で reflection するメモ

https://www.rttr.org/

C++11 で reflection できます(レイトレーシングなどでいう**reflection(反射)**ではない).

rtti(runtime time information)が無効でも使えます.

たとえば, 文字列からクラスを生成したり, enum の値と文字の変換を容易にしたり, class を JSON へシリアライズできたり, python のような動的型プログラミングっぽいことができたりします.

C++11 の場合, 自前 variant 実装(実質的には any)などを使っているようです

## 使い方

オフィシャルページにあるように, 

RTTR_REGISTRATION で, class に対して non-intrusive に記述できます.


```cpp
// .cpp
RTTR_REGISTRATION
{
    rttr::registration::class_<player>("player")
        .constructor<>()
        .constructor<int>()
        .constructor<int, int>()
        .property("hp", &player::get_hp, &player::set_hp)
        .property("speed", &player::speed)
        .property_readonly("bullets", &player::bullets)
        .method("shoot", &player::shoot)
        .method("boost", &player::boost)
        ;
}
```

pybind11 っぽいですね.

RTTR_REGISTRATION は .cpp に記述できます.

class の継承を有効にする場合は, ちょっと class に RTTR_ENABLE(); マクロの定義がいるかもしれません
(.h に rttr を expose してちょっとやだ)

コンストラクタの振る舞いを変えたり, shared_ptr でクラスを生成するなども指定できますが, 基本 RAII 的(struct 的)な感じでクラス設計をしておくのがよいでしょう.

## json

example に json のサンプルがあります.
バックエンドとしては rapidjson を使っています.

## 文字列から static な型へ戻す

json example を参考にします.

たとえば, Maya の ASCII ファイルフォーマットでは以下のような感じなります.

```
createNode transform -n "xform01";
  setAttr ".t" -type "double3" 1.0 2.0 3.0;
  setAttr ".r" -type "double3" 0.0 0.0 9.0;
  setAttr ".s" -type "double3" 1.0 1.0 1.0;
  ...

  // -type は省略したり, float3 なども受け付ける
  setAttr ".t" 1.0 2.0 3.0;
  setAttr ".t" -type "float3" 1.0 2.0 3.0;

```

ここから, C++ で具体的なクラス(struct)に復元したいとします.

```cpp
struct Transform
{
  std::array<double, 3> translation;
  std::array<double, 3> rotation;
  std::array<double, 3> scale;
}
```

python だとぺろっといけますが, C++ だとなかなか面倒です.
よくやるのは bison/flex, peg とかでパーサーを書くことですが, だいたいの型が決まっているケースだったり, ちょっとした規模のコードで毎回パーサーを書くというのも面倒です.

とはいえ, それぞれで if とか case で判定文をリストアップして対応というのもなかなか面倒...
(C++17 あたりではテンプレートメタプログラミングでなにかうまい方法あるかもですが)

rttr だと以下のようにできます.

まず, struct を登録します. これは .cpp に記述できるので non-intrusive にできます

```cpp

RTTR_REGISTRATION {
  using namespace rttr;

  registration::class_<Transform>("Transform")
      .property(".t", &Transform::translation)
      .property(".s", &Transform::scale)
      .property(".r", &Transform::rotation);

}
```

```cpp

bool ParseDouble3(const std::string &value, std::array<double, 3> *out) {
  std::array<double, 3> v;
  auto view = scn::make_view(value);
  auto ret = scn::scan(view, "{} {} {}", v[0], v[1], v[2]);
  std::cout << "value = " << v[0] << ", " << v[1] << ", " << v[2] << "\n";

  if (ret) {
    (*out) = v;
    return true;
  }
  return false;
}

rttr::variant parse_basic_value(const std::string &type_, const std::string &s,
                                const std::string &aux_type_ = std::string()) {
  std::string ty = type_.empty() ? aux_type_ : type_;

  if (ty == "double3") {
    std::array<double, 3> v;
    if (ParseDouble3(s, &v)) {
      return v;
    }
  }

  return rttr::variant();
}
```

double3 を文字列からパースするのは scnlib https://github.com/eliaskosunen/scnlib を使いました.
std::regex でもいけるでしょうが, ちょっとしたパースなら scnlib のほうが python っぽく `{}` でパターンマッチ + 型チェクもしてくれて楽です. 

可変長のリスト(`std::vector`)にも対応しています. 
https://github.com/eliaskosunen/scnlib/blob/master/test/list.cpp 

パースの結果は `rttr::variant` で返します. これは実質的には `std::any` です(`std::variant` のように事前にとりうる型を定義しておく必要がない)

ここからが, rttr の真価の発揮です. 事前にある程度は tokenize されているとします.

```cpp
    std::string attr_name; // ".t", ".r", ...
    std::optional<std::string> type; // e.g. "double3"
    std::string value_str; // e.g. "1.0 2.0 3.0"

    Transform xform;
    rttr::property attr = rttr::type::get(xform).get_property(attr_name);
    if (attr) {
      std::string ty = GetAttrTypeOfTransform(attr_name);

      std::string aux_ty; // type specifier from setAttr command.
      if (type) {
        aux_ty = type.value();
      }

      rttr::variant v = parse_basic_value(ty, value_str, aux_ty);

      if (v.convert(attr.get_type())) {
        attr.set_value((*xform), v);
        NANOLOG_INFO("attr: {}, ty: {}(aux ty: {}), set: {}", attr_name, ty, aux_ty, value_str);
      }

    }
```
 
get_property で, 文字列(e.g. ".t")から, Transform の変数(e.g. `Transform::translation`)を取得します. rttr::variant v が, パースした値になります.

これを, `convert` で, 欲しい型(`attr.get_type()`)に変換できるかチェックし, 変換できれば, `set_value` で実際に値をセット(e.g. `Transform::translation = {1.0, 2.0, 3.0}`)できます!

これで, シッカリ型チェックしつつ, 文字列から具体的な struct/class へ復元するのを比較的少ないコードで実現できました!


## 注意点

登録していないプロパティなどは, エラーはださずなにもしませんので注意です.

## TODO

* [ ] serialize と deserialize で RTTR_REGISTRATION を分ける方法を調べる
  * [ ] 現状別クラス定義しないとダメそう https://github.com/rttrorg/rttr/issues/259 
* [ ] 登録していないときはエラーを出すコンパイルオプションがあるか調べる
* [ ] 動的にクラスにプロパティ, メソッドを追加できる方法があるか調べる(std::any と組み合わせるといけるか?)
* [ ] cppast https://github.com/foonathan/cppast と組み合わせてなにかできないか調べる