C++で宣言したenum classの名前のままLuaスクリプトで面倒でない感じに使いたい (C++/Boost/Sol/Siv3D)

# なにがしたいのか

```cpp
enum class MyEnum {
    A,
    B,
    C,
}
```

こんな enum class があったとして、Lua から

```lua
local e = MyEnum.B
```

という感じで使いたい。
だけどいちいちLua側で `MyEnum={}; MyEnum.A=0; MyEnum.B=1; ...` みたいなことを手作業でやりたくない。


# アプローチ

* マクロを使ってどうにかする
* enum の数値と文字列のマップを用意して、C++ 側から Lua の変数を設定する
* これらの記事を参考にさせてもらいました
    * http://www.singularpoint.org/blog/c/c-enum-macro-2/
    * http://kryozahiro.hateblo.jp/entry/2015/09/13/202702
    * 勉強になりました。


# ソースコード

開発環境：

* Windows 10
* Visual Studio Community 2015 Update3
* Boost 1.60.0 （bimap が便利そうだったので）
* Lua 5.2.4
* [Sol 2.17.3](https://github.com/ThePhD/sol2/releases/tag/v2.17.3)
* [Siv3D August 2016 v2](https://github.com/Siv3D/Reference-JP/wiki/%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89%E3%81%A8%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) （文字列処理が便利なので）


```cpp
#include <Siv3D.hpp>
#include <boost/bimap/bimap.hpp>
#include <sol.hpp>
#pragma comment(lib, "lua52.lib")

#define ENUM_CLASS(NAME, ...) \
enum class NAME : int { __VA_ARGS__ }; \
struct NAME##_ { \
	using bimap_t = boost::bimaps::bimap<NAME, String>; \
	using value_t = bimap_t::value_type; \
	using enum_t = NAME; \
	template <class T> static String toString(const T v) { return bimap_holder::get().left.at((NAME)v); } \
	static auto toEnum(const String& v) { return bimap_holder::get().right.at(v); } \
	static const size_t size() { return bimap_holder::get().size(); } \
	static const String name() { return Widen(#NAME); } \
	template <class Fn> static void forEach(Fn f) { \
		using iterator = bimap_t::left_const_iterator; \
		for (iterator it = bimap_holder::get().left.begin(); it != bimap_holder::get().left.end(); it++) { \
			f(it->first, it->second); \
		} \
	} \
private: \
	struct bimap_holder { \
		static bimap_t& get() { \
			static bimap_t m_; \
			static bool init = false; \
			if (!init) { \
				init = true; \
				initialize(); \
			} \
			return m_; \
		} \
	}; \
	static void initialize() { \
		bimap_t& m = bimap_holder::get(); \
		Array<String> arr = Widen(#__VA_ARGS__).split(L','); \
		int enum_val = 0; \
		for (auto s : arr) { \
			Array<Match> match = Regex::Search(s, L"\\s*=\\s*(.+)$"); \
			if (!match.empty()) { \
				enum_val = Parse<int>(match[0].str(1)); \
			} \
			m.insert(value_t((NAME)enum_val, Regex::ReplaceAll(s, L"\\s*=.+$", L"").trim())); \
			++enum_val; \
		} \
	} \
};

template <class Enum>
void registerEnum(sol::state& lua)
{
	lua[Enum::name().str()] = lua.create_table();
	Enum::forEach([&](Enum::enum_t e, String s) {
		lua[Enum::name().str()][s.str()] = (int)e;
	});
}

// enum class TestEnum の宣言
// このようにする：
ENUM_CLASS(TestEnum,
	A = 100,
	B,
	C,
	D,
	X,
	Y,
	Z);


void Main()
{
	// Lua初期化
	sol::state lua;
	lua.open_libraries();

	// Luaの環境にenumの値を設定
	registerEnum<TestEnum_>(lua);

	// Lua スクリプトで TestEnum.B という書き方ができる
	lua.script("function f() return TestEnum.B end");

	Println((int)(lua["f"]())); // => 101 (TestEnum::B)

	WaitKey();
}
```

# 実行結果

![L.png](https://qiita-image-store.s3.amazonaws.com/0/35599/578349c4-fe26-7424-ad80-230eea26a76c.png)


# 雑記

* enum class A に対して A_ というクラスができてしまうところが気持ち悪いかもしれない
* bimap を使わなくても別に std::map で find してもいい
* Sol が string と wstring を良い感じに勝手に変換してくれるから、s3d::String を Narrow() しなくていい