游戏开发论坛

 找回密码
 立即注册
搜索
查看: 5312|回复: 7

模板产生式编程,奇技淫巧也是临门一脚

[复制链接]

6

主题

95

帖子

103

积分

注册会员

Rank: 2

积分
103
发表于 2007-1-7 20:27:00 | 显示全部楼层 |阅读模式
我编写Lua中C API的C++封装时,面临这样一个问题:让Lua使用C++中用户定义的类接口和函数,大概形式就是:
C++:
class C
{
public:
T f(A1,A2);
};


Lua:
function run_host(c,a1,a2)
return c:f(a1,a2)
end

这个时候最直接的方法,就是做一个metatable mt,使mt.__index = mt,并设置mt["f"] = fun。fun是一个lua_CFunction,lua.h中定义typedef int (*lua_CFunction) (lua_State *L) )。然后每次将C对象的地址当作void*送到Lua端时,将其metatable设置为mt(lua只允许table和userdata拥metatable ),这样脚本端调用c的f时会查找出fun并调用fun(c,a1,a2)。
注意一下可以发现,调用C::f,有固定的机械行为:
int fun(lua_State* L)
{
C* c;
GetValue(&c,L,1);
A1 a1;
GetValue(&a1,L,2);
A2 a2;
GetValue(&a2,L,3);
//取参数
PushValue(L,c->f(a1,a2));
//调用,将结果压栈
return 1;
}

也就是说对于所有型为T(C::*f)(A1,A2)的成员函数,都存在一个lua_CFunction fun,使fun(c,a1,a2)等效于c->f(a1,a2)。
只要把这个套路告诉编译器,让它见到T(C::*f)(A1,A2)就给我生成一个fun就完事了。
主要问题就是这个fun。说的清,道的明,但是怎么样让编译器反映过来是个问题。

出发条件
1)非类型模板参数包括成员函数指针。
2)参数与返回值确定的函数也可以是模板函数,可对其特化版本取址,比如:
template<class T>
void Delete(void* p)
{
delete reinterpret_cast<T*>(p);
}
void(* pdelete)(void*) = Delete<int>;

先考虑怎样对固定类型Test生成这样一个lua_CFunction:
class Test
{
public:
int f1(const char*);
int f2(const char*);
};

template<int(Test::*mf)(const char*)>
int lua_fun(lua_State* L)
{
  Test* t;
  GetValue(&t,L,2);
  const char* s;
  GetValue(&s,L,2);
  PushValue(L,(*t.*mf)(s))

  return 1;
}

为了这一步能走通,首先提供了两大组重载函数GetValue和PushValue。
lua_CFunction f1 = lua_fun<&Test::f1>,f2 = lua_fun<&Test::f2>;

现在将Test,int,const char* 都泛化:

template<class T,class R,class A>
class LuaFunMaker
{
template<R(T::*mf)(A)>
static lua_fun(lua_State* L)
{
  T* t;
  GetValue(&t,L,2);
  A s;
  GetValue(&s,L,2);
  PushValue(L,(*t.*mf)(s))

  return 1;
}
};

lua_CFunction f = LuaFunMaker<int,Test,const char*>::lua_fun<&Test::f1>;

这里要注意的是,现有模板机制不能对一个任意类型的非类型模板参数,一次推导出该参数的类型。
不使用偏特化你不能写出可以这样使用的模板:
t<&C::f>
t<32>//不过可以写出:t<int,32>
//C任意类型
所以需要先推导出C的类型,再取相关的非类型模板参数,形如:f(exp).f<exp>();

核心部分基本完工了,但可以再尽善尽美一下,最终解决方案是加了一个小小间接层次,使用如下:
lua_CFunction f = LuaFunMake(&Test::f1).get<&Test::f1>();
定义一个宏辅助减少输入
#define FUN2LUA(mf) LuaFunMake(mf).get<mf>()
lua_CFunction f = FUN2LUA(&Test::f1);
至此使用已无额外记忆负担。
最终的实现比这个还要复杂很多,但是所用的模板技术,早有文章介绍,所以不再重复。

总结:这里解决的是这样一种问题:特殊情况下,虽然存在一种生成新类或函数的最直观的解决方案(本例为生成lua_CFunction),但是缺乏一般语言特性支持,此时可以考虑使用复杂的模板技巧,因为模板的推演能力能帮助生成新方法。

闲话:C++的很多特殊技巧并不推荐常驻备选方案,但是当你有一个逻辑上非常有效,现实中却难以下手的计划时,奇技淫巧可能是最后一支机动兵力。

我知道还有lua-bind等可用,但本库的目的和实现都是特别的。
该库我仍在更新,下载地址为:
http://free5.ys168.com/?qslash
本文相关内容见
lua\functionwrap.h
lua\classregister.h

使用介绍在
http://www.luachina.net/bbs/624/ShowPost.aspx

ACGFan = qslash

编译器使用VC8.0

第一次写比较长的贴,问题肯定不少,如果指出我会修改。

2

主题

27

帖子

27

积分

注册会员

Rank: 2

积分
27
发表于 2007-1-8 03:00:00 | 显示全部楼层

Re:模板产生式编程,奇技淫巧也是临门一脚

http://lua-users.org/wiki/SimplerCppBinding
也是用模板

6

主题

95

帖子

103

积分

注册会员

Rank: 2

积分
103
 楼主| 发表于 2007-1-8 10:35:00 | 显示全部楼层

Re:模板产生式编程,奇技淫巧也是临门一脚

功能不一样,我的设计不需要侵入修改类本身,类本身可以对Lua的存在一无所知,只要在任意CPP文件中对定义调用方式即可。
比如:
BEGIN_CLASS2LUA(std::vector<int>)
LUA_METHOD(push_back)
END_CLASS2LUA

    vector<int> v;
    d["vector"] = v;
    (d["vector"]->*"push_back")(3);


    cout<<v.size()<<endl<<v[0]<<endl;
输出
1
3

1

主题

11

帖子

16

积分

新手上路

Rank: 1

积分
16
发表于 2007-1-8 14:00:00 | 显示全部楼层

Re:模板产生式编程,奇技淫巧也是临门一脚

非常出色的东西,多谢楼主。

193

主题

870

帖子

903

积分

高级会员

Rank: 4

积分
903
QQ
发表于 2007-1-12 01:28:00 | 显示全部楼层

Re: 模板产生式编程,奇技淫巧也是临门一脚

黑猫白猫 能抓住老鼠就是好猫

楼主 赞一个。

之前小弟我也写了一个 从C++ 调用lua的封装。
挺漂亮,可惜不是很实用。所以最后就没用上。

使用方法是
lua_State * L = luaL_newstate();;
LuaFunction lua(L);
lua::exe(“function1”,result, parameter0,  parameter1) ;//六个以内参数,常用数据类型支持。

里面都是使用内联和模板,效率不差,展开代码也都和手写一样,只是好看一些。





  1. #ifndef LUAFUNCTION_H
  2. #define LUAFUNCTION_H


  3. extern "C" {
  4.     #include "lua.h"
  5.     #include "lualib.h"
  6.     #include "lauxlib.h"
  7.     }

  8. class VOID{};
  9. class NUL{};
  10. template<class Q>
  11. class Push{};

  12. template<>
  13. class Push<int>
  14. {
  15. public:
  16.         inline static void push(lua_State * L, int q)
  17.         {
  18.                 lua_pushnumber(L, q);
  19.         }
  20. };


  21. template<>
  22. class Push<float>
  23. {
  24. public:
  25.         inline static void push(lua_State * L, int q)
  26.         {
  27.                 lua_pushnumber(L, q);
  28.         }
  29. };


  30. template<>
  31. class Push<bool>
  32. {
  33. public:
  34.         inline static void push(lua_State * L, bool q)
  35.         {
  36.                 lua_pushboolean(L, q);
  37.         }
  38. };


  39. template<>
  40. class Push<const char *>
  41. {
  42. public:
  43.         inline static void push(lua_State * L, const char * q)
  44.         {
  45.                 lua_pushstring(L, q);
  46.         }
  47. };


  48. template<>
  49. class Push<char *>
  50. {
  51. public:
  52.         inline static void push(lua_State * L, const char * q)
  53.         {
  54.                 Push<const char *>::push(L, q);
  55.                 //lua_pushstring(L, q);
  56.         }
  57. };

  58. template<>
  59. class Push<std::string>
  60. {
  61. public:
  62.         inline static void push(lua_State * L, std::string & q)
  63.         {
  64.                 Push<const char *>::push(L, q.c_str());
  65.                 //lua_pushstring(L, q.c_str());
  66.         }
  67. };


  68. template<>
  69. class Push<const NUL>
  70. {
  71. public:
  72.         inline static void push(lua_State * L, NUL)
  73.         {
  74.                 lua_pushnil(L);
  75.         }
  76. };




  77. template<class R>
  78. class Pop{};

  79. template<>
  80. class Pop<int>
  81. {
  82. public:
  83.         inline static void pop(lua_State * L, int &r,int n)
  84.         {
  85.                 lua_pcall(L, n, 1, 0);
  86.                 r = (int)lua_tonumber(L, -1);
  87.         }
  88. };


  89. template<>
  90. class Pop<float>
  91. {
  92. public:
  93.         inline static void pop(lua_State * L, float &r,int n)
  94.         {
  95.                 lua_pcall(L, n, 1, 0);
  96.                 r = (float)lua_tonumber(L, -1);
  97.         }
  98. };



  99. template<>
  100. class Pop<bool>
  101. {
  102. public:
  103.         inline static void pop(lua_State * L, bool &r,int n)
  104.         {
  105.                 lua_pcall(L, n, 1, 0);
  106.                 r = lua_toboolean(L, -1);
  107.         }
  108. };

  109. template<>
  110. class Pop<std::string>
  111. {
  112. public:
  113.         inline static void pop(lua_State * L, std::string &r,int n)
  114.         {
  115.                 lua_pcall(L, n, 1, 0);
  116.                 r = lua_tostring(L, -1);
  117.         }
  118. };



  119. template<>
  120. class Pop<char *>
  121. {
  122. public:
  123.         inline static void pop(lua_State * L, char *r,int n)
  124.         {
  125.                 lua_pcall(L, n, 1, 0);
  126.                 strcpy(r, lua_tostring(L, -1));
  127.        
  128.         }
  129. };


  130. template<>
  131. class Pop<const VOID>
  132. {
  133. public:
  134.         inline static void pop(lua_State * L, const VOID &r,int n)
  135.         {
  136.                 lua_pcall(L, n, 0, 0);
  137.         }
  138. };

  139. class LuaFunction
  140. {
  141. private:
  142.         lua_State * _L;
  143. public :

  144.         const static VOID VOID;
  145.         const static NUL NUL;
  146.         inline LuaFunction(lua_State * L):_L(L){}

  147.         template<class R>
  148.         inline void exe(const char * fun, R &r)
  149.         {
  150.                 lua_getglobal(_L, fun);
  151.                 Pop<R>::pop(_L, r, 0);
  152.         }

  153.         template<class R, class P0>
  154.         inline void exe(const char * fun, R &r, P0 p0)
  155.         {

  156.                 lua_getglobal(_L, fun);
  157.                 Push<P0>::push(_L, p0);
  158.                 Pop<R>::pop(_L, r,1);
  159.         }

  160.         template<class R, class P0,class P1>
  161.         inline void exe(const char * fun, R &r, P0 p0, P1 p1)
  162.         {

  163.                 lua_getglobal(_L, fun);
  164.                 Push<P0>::push(_L, p0);
  165.                 Push<P1>::push(_L, p1);
  166.                 Pop<R>::pop(_L, r, 2);
  167.         }


  168.         template<class R, class P0,class P1,class P2>
  169.         inline void exe(const char * fun, R &r, P0 p0, P1 p1,P2 p2)
  170.         {

  171.                 lua_getglobal(_L, fun);
  172.                 Push<P0>::push(_L, p0);
  173.                 Push<P1>::push(_L, p1);
  174.                 Push<P2>::push(_L, p2);
  175.                 Pop<R>::pop(_L, r, 3);
  176.         }


  177.         template<class R, class P0,class P1,class P2, class P3>
  178.         inline void exe(const char * fun, R &r, P0 p0, P1 p1, P2 p2, P3 p3)
  179.         {

  180.                 lua_getglobal(_L, fun);
  181.                 Push<P0>::push(_L, p0);
  182.                 Push<P1>::push(_L, p1);
  183.                 Push<P2>::push(_L, p2);
  184.                 Push<P3>::push(_L, p3);
  185.                 Pop<R>::pop(_L, r, 4);
  186.         }


  187.         template<class R, class P0,class P1,class P2, class P3, class P4>
  188.         inline void exe(const char * fun, R &r, P0 p0, P1 p1, P2 p2, P3 p3, P4 p4)
  189.         {

  190.                 lua_getglobal(_L, fun);
  191.                 Push<P0>::push(_L, p0);
  192.                 Push<P1>::push(_L, p1);
  193.                 Push<P2>::push(_L, p2);
  194.                 Push<P3>::push(_L, p3);
  195.                 Push<P4>::push(_L, p4);
  196.                 Pop<R>::pop(_L, r, 5);
  197.         }


  198.         template<class R, class P0,class P1,class P2, class P3, class P4, class P5>
  199.         inline void exe(const char * fun, R &r, P0 p0, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)
  200.         {

  201.                 lua_getglobal(_L, fun);
  202.                 Push<P0>::push(_L, p0);
  203.                 Push<P1>::push(_L, p1);
  204.                 Push<P2>::push(_L, p2);
  205.                 Push<P3>::push(_L, p3);
  206.                 Push<P4>::push(_L, p4);
  207.                 Push<P5>::push(_L, p5);
  208.                 Pop<R>::pop(_L, r, 6);
  209.         }

  210. };

  211. const VOID LuaFunction::VOID;
  212. const NUL LuaFunction::NUL;
  213. #endif
复制代码


虽然没有用上,但在这里供大家批评吧。Loki里面的函数对象比较好,任意参数类型任意数量。有时间能适配一下Lua应该不错。

6

主题

95

帖子

103

积分

注册会员

Rank: 2

积分
103
 楼主| 发表于 2007-1-12 10:44:00 | 显示全部楼层

Re:模板产生式编程,奇技淫巧也是临门一脚


谢谢。咱俩对“不同类型使用统一接口”的原始冲动相同,所以代码的body一样,只不过我比较懒,用的是函数重载。
Loki::Function/Loki::Functor配lua的想法很华丽。如果我使用在lua-stack上分配full-userdata的办法,在调用的lua-cfunction时候再加一个间接层次,即设置__call的metamethod,然后读出Loki::Function或Loki::Functor,那么确实可以做到把Loki::Function/Loki::Functor压入lua,然后由lua端调用。lua-bind似乎就是这个路子。
补充一下初始设计原则:
1)尽量把使用者学习负担降到0。
//所以调用lua-funtion是重载(),提领表成员用的是可以无限镶嵌的[],交换数据尽量用 = 。常见写法就是Set(res) = r[..][..](..),r1[..][..] = r2[..][..]。这样比用Call,Get之类成员函数直观。
2)尽量只表达C++和lua共有的概念。
3)不需要lua端了解C++,也不需要C++端了解lua,一切黏合工作在本库内完成。
4)尽量避免诱使用户的注意力放在本库上,使其能集中注意力在C++和lua的代码上。
所以我的用户定义类型接口是在全局域里用宏静态注册的(实际就是完成函数),不需要在函数体内写d.Register<..>()。希望用户能避免在函数体内作手续性质的代码。

0

主题

199

帖子

199

积分

注册会员

Rank: 2

积分
199
发表于 2007-1-20 12:17:00 | 显示全部楼层

Re: 模板产生式编程,奇技淫巧也是临门一脚

Loki也不可能做到任意参数的。
好像最多是16个参数吧。一般不会有超过16个这么变态的函数了。
过度使用模版往往导致除错很麻烦。这一方面是编译器对模版支持不够,另一方面是模版本身的技巧已经太复杂了。

6

主题

95

帖子

103

积分

注册会员

Rank: 2

积分
103
 楼主| 发表于 2007-1-23 15:55:00 | 显示全部楼层

Re:模板产生式编程,奇技淫巧也是临门一脚

我写这篇的意思就是:把产生式编程手法作为功能而非框架使用。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-26 08:06

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表