Вопрос

I am using x-macros to reduce the amount of repetition and code duplication while implementing a Lua interface for the game Bitfighter. The following code works fine:

  //                            Fn name     Valid param profiles  Profile count                           
#  define TELEPORTER_LUA_METHOD_TABLE \
      TELEPORTER_LUA_METHOD_ITEM(addDest,    ARRAYDEF({{ PT,  END }}), 1 ) \
      TELEPORTER_LUA_METHOD_ITEM(delDest,    ARRAYDEF({{ INT, END }}), 1 ) \
      TELEPORTER_LUA_METHOD_ITEM(clearDests, ARRAYDEF({{      END }}), 1 ) \


// BLOCK A Start
const luaL_reg Teleporter::luaMethods[] =
{
#  define TELEPORTER_LUA_METHOD_ITEM(name, b, c) { #name, luaW_doMethod<Teleporter, &Teleporter::name > },
      TELEPORTER_LUA_METHOD_TABLE
#  undef TELEPORTER_LUA_METHOD_ITEM
   { NULL, NULL }
};
// BLOCK A End

  /* Generates the following:
  const luaL_reg Teleporter::luaMethods[] =
  {
       { "addDest", luaW_doMethod<Teleporter, &Teleporter::addDest > }
       { "delDest", luaW_doMethod<Teleporter, &Teleporter::delDest > }
       { "clearDests", luaW_doMethod<Teleporter, &Teleporter::clearDests > }
       { NULL, NULL }
  };
  */


// BLOCK B Start
const LuaFunctionProfile Teleporter::functionArgs[] =
{
#  define TELEPORTER_LUA_METHOD_ITEM(name, profiles, profileCount) { #name, profiles, profileCount },
      TELEPORTER_LUA_METHOD_TABLE
#  undef TELEPORTER_LUA_METHOD_ITEM
   { NULL, { }, 0 }
};
// BLOCK B End


  /* Generates the following:
  const LuaFunctionProfile Teleporter::functionArgs[] =
  {
     { "addDest",    {{ PT,  END }}, 1 }
     { "delDest",    {{ INT, END }}, 1 }
     { "clearDests", {{      END }}, 1 }
     { NULL, { }, 0 }
  };
  */

#undef TELEPORTER_LUA_METHOD_TABLE

So far, so good.

Except that I do essentially the same thing in dozens of classes. What I'd REALLY like to do is define the method table in each class (which can be called anything), then define two macros that can be called like this:

GENERATE_LUA_METHODS(Teleporter, TELEPORTER_LUA_METHOD_TABLE)
GENERATE_FUNCTION_PROFILE(Teleporter, TELEPORTER_LUA_METHOD_TABLE)

to avoid all the repeated code above in blocks A & B. The obvious way is to use a nested macro, but that is, unfortunately, illegal.

Is there a better way?


SOLUTION


When I posted this question, I was pretty sure the answer would be "can't be done." Instead, I got two approaches, one of which was exactly what I was looking for. There was also a good discussion of the pitfalls of macros (there are many), with some alternative approaches suggested. The implementation I developed based on the accepted answer is clean and easy to understand, with the dirty macro stuff conveniently out-of-sight.

In a hidey-hole somewhere:

#define ARRAYDEF(...) __VA_ARGS__   // Don't confuse the preprocessor with array defs


////////////////////////////////////////
////////////////////////////////////////
//
// Some ugly macro defs that will make our Lua classes sleek and beautiful
//
////////////////////////////////////////
////////////////////////////////////////
//
// See discussion of this code here:
// http://stackoverflow.com/questions/11413663/reducing-code-repetition-in-c
//
// Start with a definition like the following:
// #define LUA_METHODS(CLASS, METHOD) \
//    METHOD(CLASS, addDest,    ARRAYDEF({{ PT,  END }}), 1 ) \
//    METHOD(CLASS, delDest,    ARRAYDEF({{ INT, END }}), 1 ) \
//    METHOD(CLASS, clearDests, ARRAYDEF({{      END }}), 1 ) \
//

#define LUA_METHOD_ITEM(class_, name, b, c) \
  { #name, luaW_doMethod<class_, &class_::name > },

#define GENERATE_LUA_METHODS_TABLE(class_, table_) \
  const luaL_reg class_::luaMethods[] =            \
  {                                                \
    table_(class_, LUA_METHOD_ITEM)                \
    { NULL, NULL }                                 \
  }

// Generates something like the following:
// const luaL_reg Teleporter::luaMethods[] =
// {
//       { "addDest",    luaW_doMethod<Teleporter, &Teleporter::addDest >    }
//       { "delDest",    luaW_doMethod<Teleporter, &Teleporter::delDest >    }
//       { "clearDests", luaW_doMethod<Teleporter, &Teleporter::clearDests > }
//       { NULL, NULL }
// };

////////////////////////////////////////

#define LUA_FUNARGS_ITEM(class_, name, profiles, profileCount) \
  { #name, profiles, profileCount },

#define GENERATE_LUA_FUNARGS_TABLE(class_, table_)  \
  const LuaFunctionProfile class_::functionArgs[] = \
  {                                                 \
    table_(class_, LUA_FUNARGS_ITEM)                \
    { NULL, { }, 0 }                                \
  }

// Generates something like the following:
// const LuaFunctionProfile Teleporter::functionArgs[] =
// {
//    { "addDest",    {{ PT,  END }}, 1 }
//    { "delDest",    {{ INT, END }}, 1 }
//    { "clearDests", {{      END }}, 1 }
//    { NULL, { }, 0 }
// };

////////////////////////////////////////
////////////////////////////////////////

In each class file:

//               Fn name     Param profiles       Profile count                           
#define LUA_METHODS(CLASS, METHOD) \
   METHOD(CLASS, addDest,    ARRAYDEF({{ PT,  END }}), 1 ) \
   METHOD(CLASS, delDest,    ARRAYDEF({{ INT, END }}), 1 ) \
   METHOD(CLASS, clearDests, ARRAYDEF({{      END }}), 1 ) \

GENERATE_LUA_METHODS_TABLE(Teleporter, LUA_METHODS);
GENERATE_LUA_FUNARGS_TABLE(Teleporter, LUA_METHODS);

#undef LUA_METHODS
Это было полезно?

Решение

functional approach may solve many of your woes, but you should be aware that heavy use of preprocessor leads to code that is hard to debug. you're bound to spend much time formatting the code whenever there's a syntax error in the generated code (and you're bound to hit that when your macro use grows sufficiently); it'll also impact your mood when you need to use gdb or some such.

the following is obviously just a sketch to give you an idea.

#  define TELEPORTER_LUA_METHOD_TABLE(class_, item) \
      item(class_, addDest,    ARRAYDEF({{ PT,  END }}), 1 ) \
      item(class_, delDest,    ARRAYDEF({{ INT, END }}), 1 ) \
      item(class_, clearDests, ARRAYDEF({{      END }}), 1 ) \

#  define LUA_METHOD_ITEM(class_, name, b, c) \
  { #name, luaW_doMethod<class_, &class_::name > },

#  define LUA_FUNARGS_ITEM(class_, name, profiles, profileCount) \
  { #name, profiles, profileCount },

#define LUA_METHODS_TABLE(class_, table) \
  const luaL_reg class_::luaMethods[] = \
  { \
    table(class_, LUA_METHOD_ITEM) \
    { NULL, NULL } \
  };

#define LUA_FUNARGS_TABLE(class_, table) \
  const LuaFunctionProfile class_::functionArgs[] = \
  { \
    table(class_, LUA_FUNARGS_ITEM) \
    { NULL, { }, 0 } \
  };

LUA_METHODS_TABLE(Teleporter, TELEPORTER_LUA_METHOD_TABLE)

LUA_FUNARGS_TABLE(Teleporter, TELEPORTER_LUA_METHOD_TABLE)

#undef TELEPORTER_LUA_METHOD_TABLE

edit to answer Watusimoto's question from the comments.

Watusimoto proposes something like this:

#define LUA_METHODS_TABLE(class_) \
  const luaL_reg class_::luaMethods[] = \
  { \
    LUA_METHOD_TABLE(class_, LUA_METHOD_ITEM) \
    { NULL, NULL } \
  };

#define LUA_FUNARGS_TABLE(class_, table) \
  const LuaFunctionProfile class_::functionArgs[] = \
  { \
    LUA_METHOD_TABLE(class_, LUA_FUNARGS_ITEM) \
    { NULL, { }, 0 } \
  };


#ifdef LUA_METHOD_TABLE
# undef LUA_METHOD_TABLE
#endif

#  define LUA_METHOD_TABLE(class_, item) \
      ... class-specific definition ...

LUA_METHODS_TABLE(Teleporter)
LUA_FUNARGS_TABLE(Teleporter)

The drawback to this is that it's not clear how is LUA_METHOD_TABLE related to the two macro calls that follow. It's just as if sprintf(3) didn't take arguments and instead expected the data in global variables of specific names. From the comprehensibility point of view it's better for any piece of code to be explicit about its immediate inputs, things it works on and which are different between its uses. But the global table macro also loses on the composability front: the global macro precludes production of multiple class definitions in one go, eg. with BPP or similar.

Другие советы

This is some rather extreme preprocessor hackery, but you can do this with a few different files.

teleporter.cpp:

#define LMT_CLASS_NAME Teleporter
#define LMT_TABLE_FILE "Teleporter.lmt"
#include "lua_method_table.h"

lua_method_table.h:

#define LMT_METHOD_ITEM(name, b, c) { #name, luaW_doMethod<LMT_CLASS_NAME, &LMT_CLASS_NAME::name > },

const luaL_reg LMT_CLASS_NAME::luaMethods[] = 
    #include LMT_TABLE_FILE
    { NULL, NULL }
};

#undef LMT_METHOD_ITEM

#define LMT_METHOD_ITEM(name, profiles, profileCount) { #name, profiles, profileCount },

const LuaFunctionProfile LMT_CLASS_NAME::functionArgs[] =
{
    #include LMT_TABLE_FILE
    { NULL, { }, 0 }
}; 

#undef LMT_METHOD_ITEM

And finally teleporter.lmt:

LMT_METHOD_ITEM(addDest,    ARRAYDEF({{ PT,  END }}), 1 )
LMT_METHOD_ITEM(delDest,    ARRAYDEF({{ INT, END }}), 1 ) 
LMT_METHOD_ITEM(clearDests, ARRAYDEF({{      END }}), 1 ) 

Instead of using a macro to define the method table, it is listed in a file, teleporter.lmt, which is included twice with different definitions of LMT_METHOD_ITEM. There's no header guards in there so it can be included as many times as necessary. If you want, you could split lua_method_table.h into two files to handle the two parts separately. Just include both of them from your CPP files.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top