目的
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_stack
やusagi::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 を意識せずに呼び出しを記述できる。
- よって return 群の型引数も
- 提供する
- こんなこともあろうかと事前に用意しておいた
usagi::tuple::apply
べんり。
注意
実用する場合には例示のため端折った usagi::lua::push_stack
や usagi::lua::peek_stack
のテンプレート特殊化を std::uint8_t
からきっちり全部書いたり、そもそも lua_State*
をシャーディングして並行処理できるようにしたりとかいろいろと工夫したライブラリーが必要になります。気が向いたらというか時間がまたとれたらちまちま切り出して紹介しようと思います。