Open vinniefalco opened 12 years ago
Marton wrote: You can get rid of the pointer to member by specifying it at compile time:
template <typename T, typename FnPtr, FnPtr F>
struct method_proxy;
template <typename T, typename Ret, Ret (T::*F)( /* params */ )>
struct method_proxy<T, Ret (T::*) ( /* params */ ), F>
{
static int f(lua_State* L)
{
// ...
T* obj = /* the first param on stack */;
Ret r = (obj->*F)( /* the params */);
// ...
}
};
Should be faster, too.
And then:
#define method(name, ptr) method__<decltype(ptr), (ptr)>(name)
template <typename T>
template <typename FnPtr, FnPtr fp>
inline Class<T>& Class<T>::method__(const char* method_name)
{
typedef method_proxy<T, FnPtr, fp> proxy;
lua_pushcfunction(L, &proxy::f);
// add const member functions only to const table
if (std::is_const<typename proxy::class_type>::value)
{
lua_pushvalue(L, -1);
add_overload(L, index + 1, method_name);
}
// add all member functions to non-const table
add_overload(L, index + 2, method_name);
return *this;
}
Very nice. Compact, but dense. Is it possible to eliminate the method macro by putting another template in between method__ and proxy::f that has the <decltype (ptr), (ptr)> bit?
Marton: Can I just add a typedef ... Type
to each existing specialization of FuncTraits
in order to implement decltype
? Then I could write typename FuncTraits <ptr>::Type
as a replacement for decltype(ptr)
.
I guess your typename FuncTraits <ptr>::Type
solution should work, but you cannot get rid of specifying the type somehow: You have a typed template parameter F
and its type must come from somewhere. -- see http://stackoverflow.com/questions/9275250/class-template-deduction-from-pointer-to-member
Marton: Thinking this through, it seems that without the macro the only way to accomplish this is to move the function argument out of the parameter list and into the template argument list? So we would need:
getGlobalNamespace (L)
.beginClass <A> ("A")
.addMethod <&A::foo> ("foo");
or
getGlobalNamespace (L)
.beginClass <A> ("A")
.addMethod <A::foo> ("foo");
I think I'm okay with making this change, the syntax it not much different and eliminating the upvalue seems the right thing to do. It will take me some time though.
I added FuncTraits<...>::DeclType
(in the develop branch), it's used like this:
typedef typename FuncTraits <MemFn>::DeclType MemFnType
MemFnType
is now equivalent to decltype (MemFn)
.
Of course it needs to go into the template parameters. See the #define
in my code above. You'll need method__<typename FuncTraits <MemFn>::DeclType, MemFn>
.
I see now that the one-argument of addMethod() described above cannot be made to work..the closest you can get is addMethod <decltype <A::foo>, A::foo> ("foo")
which is unfortunate
Yes, it's awkward, hence the macro.
I'm not terribly excited about using a macro. Is there no alternative?
I'm thinking to provide both methods of registration, one with the function pointer passed as an argument and the other passed in the template parameter. If the user wants to create a macro that is possible. This way LuaBridge can offer the performance advantages if it is needed, or avoid the awkward syntax when the performance is not an issue.
I'm working on LuaBridge again and reviewing the issues. Looking at this issue, is the performance gain really significant? One of the overriding design goals for LuaBridge is to be easy to use. Macros, and awkward syntax in the template arguments are definitely at odds with this goal.
Well, I haven't measured and performance depends on the context, of course. But the advantage of this approach is that the resulting code (up to the invocation of lua_call) can be inlined, since everything is known to the compiler at compile time. The resulting code should be faster and smaller, but I guess it would be best to compare the generated assembly (with opimization turned on).
Another advantage is that this way all other proxies can forward to function_proxy, thereby decreasing code repetition. For example:
template <typename T, typename FnPtr, FnPtr F, typename Enable = void>
struct method_proxy;
template <typename T, typename Ret, typename Base, typename... Args, Ret (Base::*F)(Args...)>
struct method_proxy<T, Ret (Base::*) (Args...), F,
typename std::enable_if<std::is_base_of<Base, T>::value>::type>
{
typedef T class_type;
typedef Ret (*wrapper_type) (class_type*, Args...);
static Ret wrapper(class_type* obj, Args... args)
{
return (obj->*F)(args...);
}
static int f(lua_State* L)
{
return function_proxy<wrapper_type, &method_proxy::wrapper>::f(L);
}
};
// repeat the above for const member functions with: typedef const T class_type;
template <typename T, typename Ptr, Ptr p, typename Enable = void>
struct propget_proxy;
template <typename T, typename U, typename Base, U Base::*mp>
struct propget_proxy<T, U Base::*, mp,
typename std::enable_if<std::is_base_of<Base, T>::value>::type>
{
typedef const T class_type;
typedef U (*wrapper_type) (class_type*);
static U wrapper(class_type* obj)
{
return obj->*mp;
}
static int f(lua_State* L)
{
return function_proxy<wrapper_type, &propget_proxy::wrapper>::f(L);
}
};
template <typename T, typename U, typename Base, U (Base::*F)() const>
struct propget_proxy<T, U (Base::*) () const, F,
typename std::enable_if<std::is_base_of<Base, T>::value>::type>
: method_proxy<T, U (Base::*) () const, F>
{};
I wouldn't do such deep nesting if I weren't sure that my code is perfectly inlineable. About the quirky syntax: As long as I only see the macro, so that's no problem for me. But with overloaded member functions, where the type of the member function must be given explicitly, the macro can't be used, so that's ugly, yes:
lua::Class<Complex>(L, "Complex")
.method__<void (Complex::*) (float), &Complex::set>("set")
.method__<void (Complex::*) (const Complex&), &Complex::set>("set")
But in this case, even with the old-style proxies, you need to cast function pointers to the right type, so that's no difference. You could make a macro also for this, something like #define overloaded_method(Class, Return, Params, Name)
I think that if we continue along these lines we will end up with an implementation identical to OOLua (http://code.google.com/p/oolua/). Which is beyond the scope of LuaBridge.
This is a continuation of the discussion from another issue:
Marton wrote: Nice. I am reading your code now. In my modifications to luabridge (internal unfortunately) I have had some of the same ideas as you did. For example, to key metatables using process-wide unique ids, only that mine are generated via
But
hash_code
needs C++11. Your solution might be easier on memory, sincetypeid()
generates a string internally, however, I don't know if it's not optimized away. Also, to get the ID of aconst T
, I simply useclass_id(const T)
.Also me I put the const methods additionally into the non-const metatable to save one lookup.
If I'm not mistaken, you also implemented the change I did too, to leave a class and its metatables on stack when registering its methods, to avoid looking it up every time.
Additionally to your changes, I got rid of all closures when pushing the proxies and to calculate the class uid inside the proxies on the fly. It might be faster (haven't benchmarked it though) and for sure it simplifies code.