Real-world use of X-Macros

前端 未结 7 1212
花落未央
花落未央 2020-11-22 12:21

I just learned of X-Macros. What real-world uses of X-Macros have you seen? When are they the right tool for the job?

7条回答
  •  谎友^
    谎友^ (楼主)
    2020-11-22 12:22

    I use a pretty massive X-macro to load contents of INI-file into a configuration struct, amongst other things revolving around that struct.

    This is what my "configuration.def" -file looks like:

    #define NMB_DUMMY(...) X(__VA_ARGS__)
    #define NMB_INT_DEFS \
       TEXT("long int") , long , , , GetLongValue , _ttol , NMB_SECT , SetLongValue , 
    
    #define NMB_STR_DEFS NMB_STR_DEFS__(TEXT("string"))
    #define NMB_PATH_DEFS NMB_STR_DEFS__(TEXT("path"))
    
    #define NMB_STR_DEFS__(ATYPE) \
      ATYPE ,  basic_string* , new basic_string\
      , delete , GetValue , , NMB_SECT , SetValue , *
    
    /* X-macro starts here */
    
    #define NMB_SECT "server"
    NMB_DUMMY(ip,TEXT("Slave IP."),TEXT("10.11.180.102"),NMB_STR_DEFS)
    NMB_DUMMY(port,TEXT("Slave portti."),TEXT("502"),NMB_STR_DEFS)
    NMB_DUMMY(slaveid,TEXT("Slave protocol ID."),0xff,NMB_INT_DEFS)
    .
    . /* And so on for about 40 items. */
    

    It's a bit confusing, I admit. It quickly become clear that I don't actually want to write all those type declarations after every field-macro. (Don't worry, there's a big comment to explain everything which I omitted for brevity.)

    And this is how I declare the configuration struct:

    typedef struct {
    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,...) TYPE ID;
    #include "configuration.def"
    #undef X
      basic_string* ini_path;  //Where all the other stuff gets read.
      long verbosity;                 //Used only by console writing functions.
    } Config;
    

    Then, in the code, firstly the default values are read into the configuration struct:

    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,SETTER,...) \
      conf->ID = CONSTRUCTOR(DEFVAL);
    #include "configuration.def"
    #undef X
    

    Then, the INI is read into the configuration struct as follows, using library SimpleIni:

    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,SETTER,DEREF...)\
      DESTRUCTOR (conf->ID);\
      conf->ID  = CONSTRUCTOR( ini.GETTER(TEXT(SECT),TEXT(#ID),DEFVAL,FALSE) );\
      LOG3A(<< left << setw(13) << TEXT(#ID) << TEXT(": ")  << left << setw(30)\
        << DEREF conf->ID << TEXT(" (") << DEFVAL << TEXT(").") );
    #include "configuration.def"
    #undef X
    

    And overrides from commandline flags, that also are formatted with the same names (in GNU long form), are applies as follows in the foillowing manner using library SimpleOpt:

    enum optflags {
    #define X(ID,...) ID,
    #include "configuration.def"
    #undef X
      };
      CSimpleOpt::SOption sopt[] = {
    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,...) {ID,TEXT("--") #ID TEXT("="), SO_REQ_CMB},
    #include "configuration.def"
    #undef X
        SO_END_OF_OPTIONS
      };
      CSimpleOpt ops(argc,argv,sopt,SO_O_NOERR);
      while(ops.Next()){
        switch(ops.OptionId()){
    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,...) \
      case ID:\
        DESTRUCTOR (conf->ID);\
        conf->ID = STRCONV( CONSTRUCTOR (  ops.OptionArg() ) );\
        LOG3A(<< TEXT("Omitted ")<ID<

    And so on, I also use the same macro to print the --help -flag output and sample default ini file, configuration.def is included 8 times in my program. "Square peg into a round hole", maybe; how would an actually competent programmer proceed with this? Lots and lots of loops and string processing?

提交回复
热议问题