C++で定義したenumをそのままの名前でLuaで使いたい(Boost.Preprocessor)

例えばC++で次のように列挙体を定義して、そのままの名前でLuaスクリプト側でも使いたいわけです。

enum Enum1 {
    E1 = 0x200,
    E2,
    E3,
    E4,
};

↓↓↓こう書きたい↓↓↓

if foo == E2 then
    bar()
end

でもそれをやるには、定義した列挙体を全部Luaのグローバル変数として登録しなければなりません。

luabind::object g = luabind::globals(L);
g["E1"] = E1;
g["E2"] = E2;
g["E3"] = E2;
…

enum定義した後で、Luaの方にも登録しなければなりません。
列挙体を1個増やすと、ソースを2箇所直さないといけない…。
これをいちいちやるのは、列挙体がたくさんあるととても面倒なので、なんとかならないものかと思っていたのですが、Boost.Preprocessorを使って楽になりそうだったので試してみました。

で、こんなふうになりました。
なにをやってるかというと、列挙体として定義したいものをシーケンスに入れておいて、
・SEQ_TO_ENUMマクロで、シーケンスの内容から列挙体を定義するコードをつくる
・SEQ_TO_LUAGLOBALSマクロで、シーケンスの内容からLuaグローバル変数を登録するコードをつくる
という感じです。

#include <iostream>
#include <boost/preprocessor.hpp>
#include <lua.hpp>
#include <luabind/luabind.hpp>

// シーケンスを渡してenum定義をつくる
// seq: 定義したい列挙体の要素名を並べたシーケンス
// name: 列挙体の名前
// value: 先頭の要素の値
#define SEQ_TO_ENUM(seq, name, value) \
    enum name { \
    BOOST_PP_SEQ_HEAD(seq) = value, \
    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TAIL(seq)) \
    };

#define SEQ_TO_LUAGLOBALS_ELEM(r, data, elem) data[BOOST_PP_STRINGIZE(elem)]=elem;

// シーケンスからLuaグローバル変数への登録
// seq: 定義したい列挙体の要素名を並べたシーケンス
// L: Lua VM
#define SEQ_TO_LUAGLOBALS(seq, L) { \
    luabind::object o=luabind::globals(L); \
    BOOST_PP_SEQ_FOR_EACH(SEQ_TO_LUAGLOBALS_ELEM, o, seq) \
    }

// 列挙体を定義する。0x200からはじまるEnum1
// あとはここをいじるだけで良い!
#define ENUM1 (E1)(E2)(E3)(E4)
SEQ_TO_ENUM(ENUM1, Enum1, 0x200)

int main()
{
    lua_State* L = lua_open();
    luaL_openlibs(L);
    luabind::open(L);

    // 列挙体Enum1をLuaに登録する
    SEQ_TO_LUAGLOBALS(ENUM1, L);
    // ためしに表示してみる
    luaL_dostring(L, "print(E1,E2,E3,E4)");

    return 0;
}

こうすると、列挙体の数を増やしたいと思ったら、上記のソースで言えばENUM1のところを変更するだけで完了、となって、最初の例よりもかなり楽になりました。

と、本当はluabindのソースコードを読み解いている途中だったんですが、脇道にそれてしまいました。
boostを使ったコードを読むのは、ほんと難しいですね…。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です