少し遊んだ後、これが私が思いついたものです:
まず、別の構造体を Luna に追加し、クラス内の別のテーブルで静的関数を定義します (LunaWrapper コードのスタイルに合わせるために、好きな方法でその情報を取得できます:
struct StaticRegType {
const char *name;
int(*mfunc)(lua_State*); // pointers to static members are C-style func pointers
// and are incompatible with int(T::*mfunc)() ones
};
...
static const Luna<Foo>::StaticRegType StaticRegister[];
...
const Luna<Foo>::StaticRegType Foo::StaticRegister[] = {
{ "bar", &Foo::bar },
{ 0 }
};
さて、興味深い部分です。残念ながら、LunaWrapper を希望どおりに動作させるには、LunaWrapper をかなり変更する必要があることがわかりました。コンストラクターを呼び出す Foo という関数を作成する代わりに、Foo という名前のテーブルを作成し、__call メソッドでメタテーブルをアタッチして、Foo() コンストラクター構文を維持します (これは Luna::Register で行われます)。 :
lua_newtable(L);
... // here we'll insert our manipulations of the table
lua_setglobal(L, T::className); // setglobal will pop the table off the stack
// so we'll do whatever we want to it and then give it
// a name to save ourselves the extra lookup
メタテーブルを作成し、コンストラクターとガベージ コレクション関数を追加します。
luaL_newmetatable(L, T::className);
lua_pushstring(L, "__gc");
lua_pushcfunction(L, &Luna<T>::gc_obj);
lua_settable(L, -3);
lua_pushstring(L, "__call");
lua_pushcfunction(L, &Luna<T>::constructor);
lua_settable(L, -3);
次に、すべてのメソッドを追加する必要があります。__index メタメソッドを使用します - 2 つのオプション: 1. __index を、lua から呼び出そうとした名前を取得して関数を実行する cfunction に設定するか、または 2. __index をすべての関数を含むテーブルに設定します (両方のオプションの詳細については、こちらを参照してください)。私は後者を好みます。これにより、すべての関数をループして面倒な文字列比較を行う必要がなくなり、コピーパスタを実行して Luna のクロージャーを再利用できるからです。ただしstaticThunk
、新しいメソッドを処理する新しい関数を作成する必要があります。
static int staticThunk(lua_State *L) {
int i = (int)lua_tonumber(L, lua_upvalueindex(1));
return (*(T::StaticRegister[i].mfunc))(L);
}
関数を呼び出しているオブジェクトを取得する必要がないため、かなり単純であることに注意してください (私は、オブジェクトの適切な関数をトリガーする詳細をコンパイラーに処理させるテンプレートの抽象化も好きです) 、しかし、それはLunaの作者側の良い決断でした;))。
ここで、__index のテーブルを作成し、それにメソッドを追加する必要があります。
lua_pushstring(L,"__index"));
lua_newtable(L);
// adding the normal methods is the same as in Luna (the for-loop over T::Register)
// add the static methods by going over our new StaticRegister array (remember to use
// staticThunk for them and thunk for the members
lua_settable(L, -3); // push the __index table to the metatable
もう少しで... これがメタテーブルの裏技です - Foo テーブルを作成しました (実際にはまだ Foo という名前ではありませんが)。オブジェクト用に作成したメタテーブルもメタテーブルとして設定します。Foo.bar()
このようにして、との両方を実行できますlocal foo = Foo(); foo.bar()
。
lua_setmetatable(L, -2); // at this point the stack contains our metatable right under
// our table
lua_setglobal(L, T::className); // name the darn thing
最後に行う必要があるのは、Luna::constructor で実際にオブジェクトを構築することに関係のないものをすべて取り除くことです (追加の利点 - Register は実際にオブジェクト タイプを登録し、コンストラクターは実際にそれを割り当てるだけです。あなたは私に尋ねます)。もともとここにあったループを Register 関数に移動しました。これで完了です。
注: このソリューションの欠点の 1 つは、便宜上両方を呼び出すことができ、Foo.bar()
またを呼び出すfoo.bar()
こともできることですFoo.foo()
。これは意味がなく、同等のコンパイル中にメンバー関数を呼び出す不正な試みとして検出されます。 C/C++ プログラムですが、Lua では実行時エラーで失敗します。この動作を取り除きたい場合は、メンバー関数を含まない Foo テーブル用のメタテーブルを 1 つ作成し、それらを含むオブジェクト用に別のメタテーブルを作成する必要があります。