なにがしたいのか
enum class MyEnum {
    A,
    B,
    C,
}
こんな enum class があったとして、Lua から
local e = MyEnum.B
という感じで使いたい。
だけどいちいちLua側で MyEnum={}; MyEnum.A=0; MyEnum.B=1; ... みたいなことを手作業でやりたくない。
アプローチ
- マクロを使ってどうにかする
- enum の数値と文字列のマップを用意して、C++ 側から Lua の変数を設定する
- これらの記事を参考にさせてもらいました
ソースコード
開発環境:
- Windows 10
- Visual Studio Community 2015 Update3
- Boost 1.60.0 (bimap が便利そうだったので)
- Lua 5.2.4
- Sol 2.17.3
- Siv3D August 2016 v2 (文字列処理が便利なので)
# 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();
}
実行結果
雑記
- enum class A に対して A_ というクラスができてしまうところが気持ち悪いかもしれない
- bimap を使わなくても別に std::map で find してもいい
- Sol が string と wstring を良い感じに勝手に変換してくれるから、s3d::String を Narrow() しなくていい
