Mitch
Mitch

Reputation: 1734

Boost Preprocessor Recursion

I'm trying to create a 'C' macro (not C++) that will define and initialize static data.

For example:

STATIC_CONST_STRUCT
(
    A, a, 
    MEMBER_DATA(CONST_STR, a, "Hello, a")
    MEMBER_DATA(CONST_STR, b, "Hello, b")
    MEMBER_STRUCT
    (
        C, c, 
        MEMBER_DATA(CONST_STR, d, "Hello, d")
        MEMBER_DATA(CONST_INT, e, 1)
    )
);

Would cause the 'C' preprocessor to create:

static const struct A
{
    CONST_STR a;
    CONST_STR b;
    struct C
    {
        CONST_STR d;
        CONST_INT e;
    } c;
} =
{"Hello, a", "Hello, b", {"Hello, d", 1}};

I've tried to use the Boost Preprocessor

http://www.boost.org/doc/libs/1_54_0/libs/preprocessor/doc/

but I can't quite figure out how to make this work. My macros stop expanding. I suspect the recursive nature of the problem having an arbitrarily deep nesting is why.

The solutions I read regarding getting the preprocessor to recurse either don't work, or the description of how to get recursion to work isn't clear enough to implement a working solution.

Here's what I have so far:

#define MEMBER_DATA_TAG 0
#define MEMBER_STRUCT_TAG 1

#define MEMBER_TAG(MEMBER) BOOST_PP_SEQ_ELEM(0, MEMBER)

#define MEMBER_DATA_TYPE(MEMBER_DATA) BOOST_PP_SEQ_ELEM(1, MEMBER_DATA)
#define MEMBER_DATA_NAME(MEMBER_DATA) BOOST_PP_SEQ_ELEM(2, MEMBER_DATA)
#define MEMBER_DATA_VALUE(MEMBER_DATA) BOOST_PP_SEQ_ELEM(3, MEMBER_DATA)

#define MEMBER_STRUCT_TYPE(MEMBER_STRUCT) BOOST_PP_SEQ_ELEM(1, MEMBER_STRUCT)
#define MEMBER_STRUCT_NAME(MEMBER_STRUCT) BOOST_PP_SEQ_ELEM(2, MEMBER_STRUCT)
#define MEMBER_STRUCT_MEMBER_SEQ(MEMBER_STRUCT) BOOST_PP_SEQ_ELEM(3, MEMBER_STRUCT)

#define MEMBER_DATA(TYPE, NAME, VALUE) ((MEMBER_DATA_TAG)(TYPE)(NAME)(VALUE))
#define MEMBER_STRUCT(TYPE, NAME, MEMBER_SEQ) ((MEMBER_STRUCT_TAG)(TYPE)(NAME)(MEMBER_SEQ))

#define IS_MEMBER_STRUCT(MEMBER_SEQ_ELEM) BOOST_PP_EQUAL(MEMBER_TAG(MEMBER_SEQ_ELEM), MEMBER_STRUCT_TAG)

#define MEMBER_STRUCT_DECLARE(TYPE, NAME, MEMBER_SEQ)                            \
    struct TYPE                                                                  \
    {                                                                            \
        BOOST_PP_SEQ_FOR_EACH(MEMBER_ELEM_DECLARE, BOOST_PP_EMPTY(), MEMBER_SEQ) \
    } NAME

#define MEMBER_ELEM_DECLARE(_r, _data, MEMBER_SEQ_ELEM) \
    BOOST_PP_IIF                                        \
    (                                                   \
        IS_MEMBER_STRUCT(MEMBER_SEQ_ELEM),              \
        MEMBER_STRUCT_DECLARE                           \
        (                                               \
            MEMBER_STRUCT_TYPE(MEMBER_SEQ_ELEM),        \
            MEMBER_STRUCT_NAME(MEMBER_SEQ_ELEM),        \
            MEMBER_STRUCT_MEMBER_SEQ(MEMBER_SEQ_ELEM)   \
        ),                                              \
        MEMBER_DATA_DECLARE                             \
        (                                               \
            MEMBER_DATA_TYPE(MEMBER_SEQ_ELEM),          \
            MEMBER_DATA_NAME(MEMBER_SEQ_ELEM),          \
            MEMBER_DATA_VALUE(MEMBER_SEQ_ELEM)          \
        )                                               \
    );

#define MEMBER_DATA_DECLARE(TYPE, NAME, VALUE) TYPE NAME

#define MEMBER_VALUE_INIT(MEMBER_SEQ) \
    BOOST_PP_SEQ_FOR_EACH_I(MEMBER_VALUE_INIT_DECLARE, BOOST_PP_EMPTY(), MEMBER_SEQ);

#define MEMBER_VALUE_INIT_DECLARE(_r, _data, i, MEMBER_SEQ_ELEM) \
    BOOST_PP_COMMA_IF(i)                                         \
    BOOST_PP_IIF                                                 \
    (                                                            \
        IS_MEMBER_STRUCT(MEMBER_SEQ_ELEM),                       \
        {MEMBER_VALUE_INIT(MEMBER_SEQ_ELEM)},                    \
        MEMBER_DATA_VALUE(MEMBER_SEQ_ELEM)                       \
    )

#define STATIC_CONST_STRUCT(TYPE, NAME, MEMBER_SEQ)              \
    static const MEMBER_STRUCT_DECLARE(TYPE, NAME, MEMBER_SEQ) = \
    {                                                            \
         MEMBER_VALUE_INIT(MEMBER_SEQ)                           \
    }

Thanks.

Upvotes: 2

Views: 579

Answers (1)

HolyBlackCat
HolyBlackCat

Reputation: 96081

It can be done without boost-preprocessor.

You don't actually need recursion. Just a loop to iterate over the macro arguments twice.

I took liberty of modifying the syntax a bit, to allow for commas in types and initializers, in case someone decides to use it with C++.

STATIC_CONST_STRUCT
(
    A, a, 
    MEMBER_DATA(a, const char *) "Hello, a"
    MEMBER_DATA(b, const char *) "Hello, b"
    MEMBER_STRUCT
    (
        C, c, 
        MEMBER_DATA(d, const char *) "Hello, d"
        MEMBER_DATA(e, int) 42
    )
)

This expands to:

static const struct A
{
    const char *a;
    const char *b;
    struct C
    {
        const char *d;
        int e;
    } c;
} a =
{
    "Hello, a",
    "Hello, b",
    {
        "Hello, d",
        42,
    },
};

Implementation:

#define STATIC_CONST_STRUCT(type_, name_, ...) \
    static const struct type_ { \
        END( LOOP_DECL_0 (__VA_ARGS__) ) \
    } name_ = { \
        END( LOOP_INIT_0 (__VA_ARGS__) ) \
    };

#define MEMBER_DATA(name_, ...) )(var,name_,(__VA_ARGS__),
#define MEMBER_STRUCT(type_, name_, ...) )(open,type_ __VA_ARGS__ )(close,name_

#define IDENTITY(...) __VA_ARGS__

#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x##y

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END

#define LOOP_DECL_0() LOOP_DECL_A
#define LOOP_DECL_A(...) LOOP_DECL_BODY(__VA_ARGS__) LOOP_DECL_B
#define LOOP_DECL_B(...) LOOP_DECL_BODY(__VA_ARGS__) LOOP_DECL_A
#define LOOP_DECL_0_END
#define LOOP_DECL_A_END
#define LOOP_DECL_B_END
#define LOOP_DECL_BODY(action_, ...) CAT(LOOP_DECL_BODY_, action_)(__VA_ARGS__)
#define LOOP_DECL_BODY_var(name_, type_, ...) IDENTITY type_ name_;
#define LOOP_DECL_BODY_open(type_) struct type_ {
#define LOOP_DECL_BODY_close(name_) } name_;

#define LOOP_INIT_0() LOOP_INIT_A
#define LOOP_INIT_A(...) LOOP_INIT_BODY(__VA_ARGS__) LOOP_INIT_B
#define LOOP_INIT_B(...) LOOP_INIT_BODY(__VA_ARGS__) LOOP_INIT_A
#define LOOP_INIT_0_END
#define LOOP_INIT_A_END
#define LOOP_INIT_B_END
#define LOOP_INIT_BODY(action_, ...) CAT(LOOP_INIT_BODY_, action_)(__VA_ARGS__)
#define LOOP_INIT_BODY_var(name_, type_, ...) __VA_ARGS__,
#define LOOP_INIT_BODY_open(type_) {
#define LOOP_INIT_BODY_close(name_) },

If used with C++, IDENTITY type_ should be wrapped in std::type_identity_t<...> to allow using types such as function pointers without typedefing them. void (*)() foo; is illegal, while std::type_identity_t<void (*)()> foo; is ok.

Upvotes: 1

Related Questions