boostpythonでC++とpythonの変換器を設定する

## はじめに
C++とpython間でListやDictなど異なる型が存在しています。
その言語間の型の差分を解消するためにユーザが作成したコンバーターを設定できます。その設定方法を記載しますが、個人的にはsuite以外の方法は難度の割に利点は少ないかなと思っています。

## 環境
 - Ubuntu:18.04.2
 - python3:3.6.8

環境作成については、[ubuntuでboost pythonを使用する](https://qiita.com/mink0212/items/5a429bdc70bef2245413)に載せています。

## suiteを使用したListの変換
良くある変換はboostpythonが事前に用意してくれているsuiteを使用すれば簡単に共有できます。

### ポイント

``` C++
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

BOOST_PYTHON_MODULE(CppMod) {
    boost::python::class_<共有したい型 >("python側に公開する型名")
    .def(vector_indexing_suite<共有したい型 >());
}
```

### 実例

1.リストを持ったクラスを生成します。

``` C++:CppMod.cpp
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

class Greeting {

    public:
    std::vector<int> myList;

    void addVector(int num) {
        myList.push_back(num);
    }
};
```

2.共有したい型をboostpythonに登録します。

``` C++:CppMod.cpp
BOOST_PYTHON_MODULE(CppMod) {
    boost::python::class_<std::vector<int> >("vector<int>")
    .def(vector_indexing_suite<std::vector<int> >());
}
```

3.対象のリストをpython側へ公開します。

``` C++:CppMod.cpp
BOOST_PYTHON_MODULE(CppMod) {
    boost::python::class_<Greeting>("greeting")
    .add_property("myList", &Greeting::myList, &Greeting::addVector)
    .def("addVector", &Greeting::addVector);

    boost::python::class_<std::vector<int> >("vector<int>")
    .def(vector_indexing_suite<std::vector<int> >());
}
```

4.実行する

```
# python3
>>> import CppMod
>>> a=CppMod.greeting()
>>> a.addVector(10)
>>> a.myList
<CppMod.vector<int> object at 0x7f04afb55120>
>>> a.myList[0]
10
>>> a.addVector(2)
>>> a.myList[1]
2
>>> a.myList[1]=20
>>> a.myList[1]
20
```

## suiteを使用したMapの変換
Map用のsuiteをListと同様に指定するとpythonへMapが共有されます。

### ポイント

``` C++
#include <boost/python/suite/indexing/map_indexing_suite.hpp>

BOOST_PYTHON_MODULE(CppMod) {
    boost::python::class_<共有したい型 >("python側に公開する型名")
    .def(map_indexing_suite<共有したい型 >());
}
```

### 実例

1.マップを持ったクラスを生成します。

``` C++:CppMod.cpp
#include <boost/python.hpp>
#include <boost/python/suite/indexing/map_indexing_suite.hpp>

class Greeting {

    public:
    std::map<int, std::string> myMap;

    void addMap(int key, std::string val) {
        myMap[key] = val;
    }
};
```

2.共有したい型をboostpythonに登録します。

``` C++:CppMod.cpp
BOOST_PYTHON_MODULE(CppMod) {
    boost::python::class_<std::map<int, std::string> >("myMap")
    .def(map_indexing_suite<std::map<int, std::string> >());
}
```

3.対象のマップをpython側へ公開します。

``` C++:CppMod.cpp
BOOST_PYTHON_MODULE(CppMod) {
    boost::python::class_<Greeting>("greeting")
    .add_property("myMap", &Greeting::myMap, &Greeting::addMap)
    .def("addMap", &Greeting::addMap);

    boost::python::class_<std::map<int, std::string> >("myMap")
    .def(map_indexing_suite<std::map<int, std::string> >());
}
```

4.実行する

```
# python3
>>> import CppMod
>>> a=CppMod.greeting()
>>> a.addMap(1, "abc")
>>> a.myMap
<CppMod.myMap object at 0x7f419bd054e0>
>>> a.myMap[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'Invalid key'
>>> a.myMap[1]
'abc'
>>> a.myMap[1] = "ab"
>>> a.myMap[1]
'ab'
>>> a.myMap[2] = "cc"
>>> a.myMap[2]
'cc'
>>> type(a.myMap)
<class 'CppMod.myMap'>
```

## 独自構造体を使用した変換
独自の構造体を使用してC++からpythonへ値の変換器を登録します。

### ポイント

``` C++
struct 変換器の名前 {
    static PyObject* convert(変換対象の値)
    {
        変換の内容
        return 変換後の値
    }
};

BOOST_PYTHON_MODULE(CppMod) {
    boost::python::to_python_converter<変換対象の型, 変換器名>();
}
```

### 実例

1.変換器を作成します。今回はC++からvectorを送るとpython側ではlistに変換します。

``` C++:CppMod.cpp
struct vec_to_list_convert {
    static PyObject* convert(std::vector<int> const& vec) {
        boost::python::list pyList;
        for (auto item: vec) {
            pyList.append(item);
        }
        return boost::python::incref(pyList.ptr());
    }
};
```

2.変換器をboostpythonへ登録します。

``` C++:CppMod.cpp
BOOST_PYTHON_MODULE(CppMod) {
    boost::python::to_python_converter<const std::vector<int>, vec_to_list_convert>();
}
```

3.実験用にvectorを返却する関数の作成と公開をする

``` C++:CppMod.cpp
class Greeting {
    public:

    std::vector<int> get_list() {
        std::vector<int> cppVec;
        cppVec.push_back(1);
        cppVec.push_back(2);
        
        return cppVec;
    }

};

BOOST_PYTHON_MODULE(CppMod) {
    boost::python::to_python_converter<const std::vector<int>, vec_to_list_convert>();

    boost::python::class_<Greeting>("greeting")
    .def("get_list", &Greeting::get_list);
}

```

4.実行する

```
# python3
>>> import CppMod
>>> a=CppMod.greeting()
>>> a.get_list()
[1, 2]
>>> type(a.get_list())
<class 'list'>
```


## おわりに
いざ変換器周りのことを書こうとしたら、ほとんどsuiteで事足りてしまうことがわかりました。
正直、変換器を駆使するために汎用的にしすぎてバグを生むより素直に関数内で変換をかけてあげるほうが良い気がしています。
おそらく、大人数で開発して開発者のレベルが低い人が多い場合に変換器とか登録してあげるとある程度品質が良くなるのかもしれないけど少人数でやる分にはそこまでうまみがないなと思います。
ひとまずboostpythonはここまでにして、次はpybind11かpythonのライブラリあたりを書こうかな。