C++ & Lua : C++ から Lua へ提供するモジュールと C++ データ型と Lua ユーザーデータを簡潔に定義するヘルパーライブラリーの実装例

# 目的

前回投稿した[「C++ & Lua : C++ のネイティブデータ型を Lua へ提供し、操作する例」](http://qiita.com/usagi/items/1e9f759b1822e4eeab93) ではそれに必要な実装をコンパクトに直接済ませた。

この記事では前回の内容をヘルパーライブラリーとして整理する実装例を示す。（ヘルパーライブラリー実装の指針とする例であり、あくまでも実用的なライブラリーを実装するための足掛かりや類似のコード例について理解を深める目的の程度に留める。）

# 実装例

```cpp
#include <lua.hpp>
#include <iostream>
#include <vector>
#include <new>
#include <mutex>
#include <stdexcept>

namespace usagi
{
  namespace lua
  {
    using function_definitions_type = std::vector< luaL_Reg >;
    
    struct meta_definition_type
    {
      std::string               name;
      function_definitions_type function_definitions;
    };
    
    using meta_definitions_type = std::vector< meta_definition_type >;
    
    template < typename T, typename ... TS >
    auto make_userdata( lua_State* raw_state, const std::string& name, TS ... ts )
    {
      const auto p = lua_newuserdata( raw_state, sizeof( T ) );
      luaL_setmetatable( raw_state, name.c_str() );
      return new( p ) T( ts ... );
    }
    
    template < typename T >
    auto check_userdata( lua_State* raw_state, const std::string& name, const int narg = 1 )
    {
      if ( const auto p = luaL_checkudata( raw_state, narg, name.c_str() ) )
        return reinterpret_cast< T* >( p );
      throw std::runtime_error( "check_userdata was failed." );
    }

    namespace detail
    {
      namespace require
      {
        const function_definitions_type* pfs;
        const meta_definitions_type*     pms;
        std::mutex                       m;
      }
    }

    auto require
    ( lua_State* raw_state
    , const std::string& name
    , const function_definitions_type& function_definitions
    , const meta_definitions_type& meta_definitions = { }
    , const bool is_global = true
    )
    {
      std::lock_guard< decltype( detail::require::m ) > lock( detail::require::m );
      detail::require::pfs = &function_definitions;
      detail::require::pms = &meta_definitions;
      
      luaL_requiref
      ( raw_state
      , name.c_str()
      , []( auto raw_state )
        {
          function_definitions_type function_definitions_copy( *detail::require::pfs );
          function_definitions_copy.resize( function_definitions_copy.size() + 1 );
          
          luaL_checkversion( raw_state );
          lua_createtable( raw_state, 0, function_definitions_copy.size() );
          luaL_setfuncs( raw_state, function_definitions_copy.data(), 0 );
          
          for ( auto meta_definition : *detail::require::pms )
          {
            function_definitions_type meta_function_definitions_copy( meta_definition.function_definitions );
            meta_function_definitions_copy.resize( meta_function_definitions_copy.size() + 1 );
            
            luaL_newmetatable( raw_state, meta_definition.name.c_str() );
            
            luaL_checkversion( raw_state );
            lua_createtable( raw_state, 0, meta_function_definitions_copy.size() );
            luaL_setfuncs( raw_state, meta_function_definitions_copy.data(), 0 );
            
            lua_setfield( raw_state, -2, "__index" );
            
            lua_pop( raw_state, 1);
          }
          
          return 1;
        }
      , static_cast< int >( is_global )
      );
      
      lua_pop( raw_state, 1 );
    }
  }
}

auto main() -> int
{
  const auto raw_state = luaL_newstate();
  luaL_openlibs( raw_state );
  
  usagi::lua::require
  ( raw_state
  , "usagi"
  , { { "make_x"
      , []( auto raw_state )
        { std::cout << "usagi.make_x : " << usagi::lua::make_userdata<int>( raw_state, "x_type", static_cast<int>( luaL_checkinteger( raw_state, 1 ) ) ) << "\n"; return 1; }
      }
    , { "make_y"
      , []( auto raw_state )
        { std::cout << "usagi.make_y : " << usagi::lua::make_userdata<int>( raw_state, "y_type", static_cast<int>( luaL_checkinteger( raw_state, 1 ) ) ) << "\n"; return 1; }
      }
    }
  , { { "x_type", { { "hello", []( auto raw_state ) { std::cout << "x_type.hello : " << *usagi::lua::check_userdata<int>( raw_state, "x_type" ) << "\n"; return 0; } } } }
    , { "y_type", { { "hello", []( auto raw_state ) { std::cout << "y_type.hello : " << *usagi::lua::check_userdata<int>( raw_state, "y_type" ) << "\n"; return 0; } } } }
    }
  );
  
  luaL_dostring
  ( raw_state
  , u8R"(
      print( "hello, lua" )
      local x = usagi.make_x( 123 )
      local y = usagi.make_y( 456 )
      x:hello();
      y:hello();
    )"
  );
  
  lua_close( raw_state );
}
```

## 実行結果

```
hello, lua
usagi.make_x : 0x16443e8
usagi.make_y : 0x1644428
x_type.hello : 123
y_type.hello : 456
```

## Wandbox

- http://melpon.org/wandbox/permlink/BpTm3y7NeHyjayKa

# 概説

- `usagi::lua` 名前空間に Lua 制御のためのヘルパーライブラリーを構築すると仮定
- `usagi::lua::require` により Lua 側にモジュール的な機能とユーザー定義型を内包可能なユーザーデータ型をまとめて提供する
    - 第2仮引数 `name` : Lua 側へ与えるモジュールの名称
    - 第3仮引数 `function_definitions` : Lua モジュールへ直接実装するメンバー関数群に相当する定義群
    - 第4仮引数 `meta_definitions` : Lua モジュールが提供するユーザー定義型とそのメンバー関数群に相当する定義群
    - 第5仮引数 `is_global` : モジュールをグローバル変数に登録するか否か
- `usagi::lua::make_userdata` ヘルパー関数により C++ の任意の型をバインディングする Lua ユーザーデータを簡便に定義できる
- `usagi::lua::check_userdata` ヘルパー関数により Lua ユーザーデータにバインディングされた C++ のデータ型を簡便に取得できる

## トリック

- `usagi::lua::require` は `usagi::lua::detail` 名前空間に定義した補助的な大域変数を用いて本来ステートレスなファンクターしか扱えない Lua の C API の `lua_CFunction` 型に与えるファンクターに間接的にステートを与え `function_definitions` と `meta_definitions` を参照せしめている。
- `luaL_newlib` はマクロによる便利なヘルパー API ではあるが、これを使うと展開される `luaL_newlibtable` マクロがサイズ付きの配列（`int a[]`のような）を前提としてしているため、C++ 的には見方によってあまり美しくないし、ユーザーがこの配列に明示的にいわゆる"番兵"（配列末尾の `nullptr` 値のような）を与えなくてはならない事も見方によってはあまり美しくない。そこで低レベル API を直接展開して用いる事で、見方によっては美しい C++ ライブラリーのそとっつらを整えている。
    - 「見方によっては」としつこく書いたが、この実装例は明らかにデータ操作上は不要なオーバーヘッドが発生するからあらゆる視点で美しいとは言い難いが、低レベル API を封入し利便性と可読性の良いユーザーコードを記述するためのライブラリーとして考えると、特にこの機能は一般に繰り返して実行されたり、ユーザーを著しく待たせるほどの重い処理では無い事もあり、見方によってはこの実装もまた美しくもありえる。

```cpp
#define luaL_newlibtable(L,l)        \
  lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)

#define luaL_newlib(L,l)  \
  (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))

typedef struct luaL_Reg {
  const char *name;
  lua_CFunction func;
} luaL_Reg;

LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
```