Search code examples
c++macrosboost-preprocessor

How to handle tuples and single tokens differently in C++ preprocessing?


I want to define a INITIALIZE(...) that achieves the following

struct MyStruct {
    std::string a;
    bool b;
    char c;
    double d;
    INITIALIZE(a, (b, true), (c, '$'), d);
};

gets expanded to

struct MyStruct {
    std::string a;
    bool b;
    char c;
    double d;

    void init() {
        a = get_value<std::string>();  // use return value of get_value function
        b = true;                      // use init value from the macro
        c = '$';                       // use init value from the macro
        d = get_value<double>();       // use return value of get_value function
    }
};

Are these achievable with the help of Boost_PP macros?

T get_value<T>() is defined in a library.


Solution

  • This is almost doable, you need an extra pair of parens around things initialized by default.

     #define INIT_1(x) x = get_value<decltype(x)>();
     #define INIT_2(x, y) x = y;
     #define INIT_ONE(...) BOOST_PP_CAT(INIT_, \
                               BOOST_PP_VARIADIC_SIZE(__VA_ARGS__))(__VA_ARGS__)
     #define INIT_ONE_X(r, data, elem) INIT_ONE elem
     #define INITIALIZE(...) void init () { BOOST_PP_SEQ_FOR_EACH( \
                               INIT_ONE_X, x, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) }
    

    Now INITIALIZE((a), (b, 'a'), (c)) expands to

    void init () { a = get_value<decltype(a)>(); b = 'a'; c = get_value<decltype(c)>(); }
    

    EDIT: As suggested by @Quentin, you can get rid of extra parentheses by using a macro similar to this. Necessary changes are

    #define INIT_ONE_XX(elem)  INIT_ONE elem
    #define INIT_ONE_X(r, data, elem) INIT_ONE_XX(ENSURE_PARENS(elem))
    
    #define ENSURE_PARENS(...) \
      BOOST_PP_REMOVE_PARENS( \
              BOOST_PP_IF( \
                  BOOST_PP_IS_BEGIN_PARENS(__VA_ARGS__), \
                  (__VA_ARGS__), \
                  ((__VA_ARGS__)) \
                  ) \
                                                                              )
    

    Then INITIALIZE(a, (b, 'a'), c) also works.

    Edit: a better, less obstructive syntax could be possible, depending on what exact C++ construct we generate. See http://aantron.github.io/better-enums/ for inspiration.