LoginSignup
3
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-05-19

なにがしたいのか

enum class MyEnum {
    A,
    B,
    C,
}

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

local e = MyEnum.B

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

アプローチ

ソースコード

開発環境:

  • 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();
}

実行結果

L.png

雑記

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2