C++ variadic macro for pairs

I am aware of the MAP macro that can be used to apply a macro function to a variadic argument list. But how would you apply a macro function to pairs of variadic arguments?

What I want to create is something like this:

#define DECL_VAR(type,var)\
  type _##var;
#define DECL_GETSET(type,var)\
  type get_##var() const {return _##var;}\
  void set_##var(type val) {_##var = val;}

#define CREATE_CLASS(C, ...)\
  class C {\
    private:\
    MAP_PAIR(DECL_VAR, ODD_VA_ARG, EVEN_VA_ARG)\
    public:\
    MAP_PAIR(DECL_GETSET, ODD_VA_ARG, EVEN_VA_ARG)\
  };

CREATE_CLASS(Person, const char*, name, int, age, float, height)
// or maybe
CREATE_CLASS(Person, (const char*, name), (int, age), (float, height))

2 answers

  • answered 2019-10-08 23:48 Neil McGill

    This work for you ?

    #include <iostream>
    #include <string>
    
    #define MACRO_DECL_TYPE(t, v) t
    #define MACRO_DECL_NAME(t, v) v = {};
    #define MACRO_GET_TYPE(t, v)
    #define MACRO_GET_NAME(t, v) t get_##v(void) const { \
        std::cout << "called get() for " << #t << " " << #v << " as " << v << std::endl; \
        return v; \
    }
    #define MACRO_SET_TYPE(t, v)
    #define MACRO_SET_NAME(t, v) void set_##v(t to) { \
        v = to; \
        std::cout << "called set() for " << #t << " " << #v << " as " << v << std::endl; \
    }
    
    #define CREATE_CLASS(CLASS_NAME, MACRO_VARS)             \
    class CLASS_NAME {                                       \
    private:                                                 \
        MACRO_VARS(MACRO_DECL_TYPE, MACRO_DECL_NAME)         \
    public:                                                  \
        MACRO_VARS(MACRO_GET_TYPE, MACRO_GET_NAME)           \
        MACRO_VARS(MACRO_SET_TYPE, MACRO_SET_NAME)           \
    };
    
    #define LIST_MACRO_COMBO(list_macro1, list_macro2, type,name) \
        list_macro1(type,name) list_macro2(type,name)
    
    //
    // Example
    // g++ test.cpp --std=c++11 ; ./a.out
    //
    #define Foo_args(list_macro1, list_macro2)       \
        LIST_MACRO_COMBO(list_macro1, list_macro2, int, a) \
        LIST_MACRO_COMBO(list_macro1, list_macro2, float, b) \
        LIST_MACRO_COMBO(list_macro1, list_macro2, std::string, c) \
    
    CREATE_CLASS(Foo, Foo_args)
    
    int main (void)
    {
        Foo foo;
        foo.get_a();
        foo.get_b();
        foo.get_c();
        foo.set_a(1);
        foo.set_b(2);
        foo.set_c("hello");
        foo.get_a();
        foo.get_b();
        foo.get_c();
    }
    

    output

    g++ test.cpp --std=c++11 ; ./a.out
    called get() for int a as 0
    called get() for float b as 0
    called get() for std::string c as
    called set() for int a as 1
    called set() for float b as 2
    called set() for std::string c as hello
    called get() for int a as 1
    called get() for float b as 2
    called get() for std::string c as hello
    

  • answered 2019-10-09 01:30 aschepler

    CREATE_CLASS(Person, (const char*, name), (int, age), (float, height))
    

    This is going to be the easier option to work with, since the preprocessor syntax handles balanced parentheses, in a way so that e.g. (const char*, name) is a single argument to the macro despite containing a comma.

    So one straightforward solution would be to provide wrapper macros which accept an argument of the form (type, varname), and pass its elements to your actual two-argument macros:

    #define DECL_VAR(type,var)\
      type _##var;
    #define DECL_VAR_PAIR(pair)\
      DECL_VAR pair
    #define DECL_GETSET(type,var)\
      type get_##var() const {return _##var;}\
      void set_##var(type val) {_##var = val;}
    #define DECL_GETSET_PAIR(pair)\
      DECL_GETSET pair
    
    #define CREATE_CLASS(C, ...)\
      class C {\
        private:\
        MAP(DECL_VAR_PAIR, __VA_ARGS__)\
        public:\
        MAP(DECL_GETSET_PAIR, __VA_ARGS__)\
      };
    
    CREATE_CLASS(Person, (const char*, name), (int, age), (float, height))
    

    So for example, when the expansion of MAP(DECL_VAR_PAIR, __VA_ARGS__) in the last CREATE_CLASS line passes the one argument (int, age) to DECL_VAR_PAIR, steps of the expansion include:

    DECL_VAR_PAIR((int, age))
    DECL_VAR(int, age)   // since DECL_VAR_PAIR(x) is just DECL_VAR then x
    int _##age;
    int _age;
    

    Though if you have a bunch of things you want to do with the paired arguments, creating all those wrapper macros could get cumbersome. Instead, we can add a MAP-like macro that expects its arguments to be lists enclosed in parentheses. First, notice that in <map.h>, the steps which actually apply a macro to one of the arguments are closely related to the main MAP macro:

    #define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
    #define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)
    #define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
    

    If the argument x is already parentheses around one list of arguments to pass to the macro f, we just want parallel versions to skip adding the parentheses around x:

    #define MAP_TUPLES0(f, x, peek, ...) f x MAP_NEXT(peek, MAP_TUPLES1)(f, peek, __VA_ARGS__)
    #define MAP_TUPLES1(f, x, peek, ...) f x MAP_NEXT(peek, MAP_TUPLES0)(f, peek, __VA_ARGS__)
    #define MAP_TUPLES(f, ...) EVAL(MAP_TUPLES1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
    

    I called this MAP_TUPLES rather than MAP_PAIRS because it's not actually limited to pairs. It can pass argument lists of any size to any macro, as long as the number of macro parameters matches. You could even use a variadic macro with argument lists of varying sizes.

    A use of this MAP_TUPLES to get your CREATE_CLASS, assuming your original DECL_VAR and DECL_GETSET, looks like:

    #define CREATE_CLASS(C, ...)\
      class C {\
        private:\
        MAP_TUPLES(DECL_VAR, __VA_ARGS__)\
        public:\
        MAP_TUPLES(DECL_GETSET, __VA_ARGS__)\
      };
    
    CREATE_CLASS(Person, (const char*, name), (int, age), (float, height))
    

    See the full example at coliru.