LoginSignup
3
3

More than 5 years have passed since last update.

C++ & Lua : 任意の引数群と return 群の Lua の関数を C++ から簡単に扱えるヘルパーライブラリーの作り方

Posted at

目的

C++ コードから Lua の関数を Lua の C API をちまちま操作して呼び出すのは少々面倒くさい。そこで任意の引数群と return 群の Lua の関数を C++ から簡単に扱えるヘルパーライブラリーを用意したい。

望まれる成果

次のような Lua コード:

function hoge( a, b )
  return a + b, a - b, a / b, a .. b
end

が有効な状況で、次のような C++ コードからの Lua 関数の呼び出しと結果の取得を行い:

// Lua 関数の return 群の型はテンプレートで与える
// Lua 関数の引数群は関数名に続けて任意の型の値をほいほい並べて渡したい
// 例示の実引数 lua_state は `lua_State*` 型のオブジェクト
// 例示の実引数 `"hoge"` は Lua の関数名、 `123` は Lua 関数の第1実引数、 `456` は Lua 関数の第2実引数
// Lua 関数の実行結果は `returns` にさらりと取得したい
const auto returns = usagi::lua::pcall< int, int, float, std::string >( lua_state, "hoge", 123, 456 );

次のように計算結果を usagi::lua::pcall の戻り値から出力を行いたい:

579 <- int : Lua 関数の return 群の 1 つめ
-333 <- int : Lua 関数の return 群の 2 つめ
0.269737 <- float : Lua 関数の return 群の 3 つめ
123456 <- std::string : Lua 関数の return 群の 4 つめ

この様に扱える usagi::lua::pcall をヘルパーライブラリーとして用意したい。

作り方

  • 先に 「C++14 : 任意のタプルへ任意のファンクターを適用する apply( my_tuple, my_functor ) の作り方」 で紹介した apply も使う。そちらは usagi::tuple::apply としてライブラリー化してあり include するものとする。
  • なお、この例はあくまでもこのようなライブラリーの作り方を紹介する目的であり、完全なライブラリーを提供する目的ではないので、 usagi::lua::pcall のほかに実用上各種型について特殊化して取り揃える事になろう usagi::lua::push_stackusagi::lua::peek_stack については今回の例示に必要最小限の特殊化のみ示す事とした。
#pragma once

#include <usagi/tuple/apply.hxx>

namespace usagi
{
  namespace lua
  {
    template < typename T > auto push_stack( lua_State*, T ) { throw std::logic_error( "push_stack: not supported type." ); }
    template < > auto push_stack< int >( lua_State* lua_state, int value ) { lua_pushinteger( lua_state, value ); }

    template < typename T >
    auto peek_stack( lua_State*, const int = 1 ) { throw std::logic_error( "peek_stack: not supported type." ); }
    template < > auto peek_stack< int >( lua_State* lua_state, const int index ) { return static_cast< int > ( lua_tointeger( lua_state, index ) ); }
    template < > auto peek_stack< float >( lua_State* lua_state, const int index ) { return static_cast< float > ( lua_tonumber( lua_state, index ) ); }
    template < > auto peek_stack< std::string >( lua_State* lua_state, const int index ) { return std::string( lua_tostring( lua_state, index ) ); }

    template < typename ... OUT_TYPES,  typename ... IN_TYPES >
    auto pcall
    ( lua_State* lua_state
    , const std::string& symbol
    , IN_TYPES&& ... in_values
    ) -> std::tuple< OUT_TYPES ... >
    {
      using in_tuple_type  = std::tuple< IN_TYPES ... >;
      using out_tuple_type = std::tuple< OUT_TYPES ... >;

      lua_getglobal( lua_state, symbol.c_str() );

      usagi::tuple::apply
      ( std::forward_as_tuple( in_values ... )
      , [ = ]( auto&& value ) { push_stack( lua_state, value ); }
      );

      constexpr auto in_size  = static_cast< int >( std::tuple_size< in_tuple_type >::value );
      constexpr auto out_size = static_cast< int >( std::tuple_size< out_tuple_type >::value );

      if ( const auto r = lua_pcall( lua_state, in_size, out_size, 0 ) )
        throw std::runtime_error( "lua pcall `" + symbol + "` failed. return code is " + std::to_string( r ) );

      out_tuple_type out;
      int index = -out_size;
      usagi::tuple::apply( out, [ =, &index ]( auto& value ) { value = peek_stack< std::remove_reference_t< decltype( value ) > >( lua_state, index++ ); } );

      lua_pop( lua_state, out_size );

      return out;
    }

  }
}

実行結果

auto main() -> int
{
  auto lua_state = luaL_newstate();

  luaL_dostring
  ( lua_state
  , u8R"(
      function hoge( a, b )
        return a + b, a - b, a / b, a .. b
      end
    )"
  );

  auto r = usagi::lua::pcall< int, int, float, std::string >( lua_state, "hoge", 123, 456 );

  usagi::tuple::apply( r, []( auto value ) { std::cout << "out : " << value << " " << typeid( value ).name() << "\n"; } );

  lua_close( lua_state );
}
out : 579 i
out : -333 i
out : 0.269737 f
out : 123456 NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

Wandbox

ポイント

  • Lua 関数の引数群と return 群を std::tuple でそれぞれまとめて扱っている。
    • 提供する usagi::lua::pcall の引数や型引数でわざわざ std::tuple をユーザーがちまちま書くのはだるいため、 variadic template でよしなにそんな必要が無いインターフェースを提供している。型引数に複数の variadic template があるが、 Lua 関数の引数群は実引数で必ず与えて使う事になるので、 return 群の variadic template の型引数とは自動的に区別して扱える。
      • よって return 群の型引数も usagi::lua::pcall を使うユーザーコード側では std::tuple を意識せずに呼び出しを記述できる。
  • こんなこともあろうかと事前に用意しておいた usagi::tuple::apply べんり。

注意

実用する場合には例示のため端折った usagi::lua::push_stackusagi::lua::peek_stack のテンプレート特殊化を std::uint8_t からきっちり全部書いたり、そもそも lua_State* をシャーディングして並行処理できるようにしたりとかいろいろと工夫したライブラリーが必要になります。気が向いたらというか時間がまたとれたらちまちま切り出して紹介しようと思います。

3
3
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
3