Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use boost preprocessor to generate accessors?

For example

class A
{
    int m_x;
    float m_y;
    double m_z;

    int x() const {return m_x;}
    float y() const {return m_y;}
    double z() const {return m_z;}
};

becomes like

class A
{
    MY_MACRO((int)(float)(double), (x)(y)(z));
};

Please use boost prerocessor sequence to do it because this macro will combine with other existing macros which already use boost preprocesor sequence.

like image 874
user1899020 Avatar asked Dec 15 '22 21:12

user1899020


1 Answers

Disclaimer:You should probably wait in case a better answer appears even if you are satisfied with this answer, because I'm far from an expert and these may not be the best approaches.

1st approach:

//two different sequences
struct A
{
    MY_MACRO1((int)(float)(double),(x)(y)(z))
};

I think this approach gives the less scary-looking macro:

#define DECLARE_DATA_MEMBER1(R,TYPES,INDEX,NAME) \
BOOST_PP_SEQ_ELEM(INDEX,TYPES) BOOST_PP_CAT(m_,NAME);

#define DEFINE_ACCESSOR1(R,TYPES,INDEX,NAME) \
BOOST_PP_SEQ_ELEM(INDEX,TYPES) NAME(){ return BOOST_PP_CAT(m_,NAME); }

#define MY_MACRO1(TYPES,NAMES) \
BOOST_PP_SEQ_FOR_EACH_I(DECLARE_DATA_MEMBER1,TYPES,NAMES) \
public: \
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_ACCESSOR1,TYPES,NAMES)

MY_MACRO gets two sequences: TYPES and NAMES. In order to declare the data members I use a BOOST_PP_SEQ_FOR_EACH_I on the sequence NAMES using the macro DECLARE_DATA_MEMBER1 and having the sequence TYPES as data. This "invokes" DECLARE_DATA_MEMBER1 with 4 parameters: R which is unused (and I have no idea what it does), TYPES (the sequence of types), INDEX (tells in which iteration we are right now, starting at 0), and NAME (the element of the original NAMES sequence that corresponds with this iteration).
The "bodies" of DECLARE_DATA_MEMBER1 and DEFINE_ACCESSOR1 are simple, we simply get the INDEXth element in the types sequence, and concatenate m_ with NAME.


2nd approach:

//just one sequence but you need to put two sets of parentheses around each pair
struct B
{
    MY_MACRO2(((int, x))((float,y))((double,z)))
};

This one is still fairly simple, but has the inconvenient of having to use double parentheses.

#define DECLARE_DATA_MEMBER2(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME));

#define DEFINE_ACCESSOR2(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)(){ return BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)); }

#define MY_MACRO2(TYPES_AND_NAMES) \
BOOST_PP_SEQ_FOR_EACH(DECLARE_DATA_MEMBER2,_,TYPES_AND_NAMES) \
public: \
BOOST_PP_SEQ_FOR_EACH(DEFINE_ACCESSOR2,_,TYPES_AND_NAMES)

This time there is only one sequence so we won't need the index in the helper macros. For this reason BOOST_PP_SEQ_FOR_EACH is used on TYPES_AND_NAMES using the macro DECLARE_DATA_MEMBER2 and without passing any extra data. This macro receives three "arguments": R again unused, _ (or DATA, also unused here), and TYPE_AND_NAME (a tuple in the form (TYPE,NAME)).
In the "bodies" of the two helper macros BOOST_PP_TUPLE_ELEM is used to get either the type(with index=0) or the name(with index=1). This macro needs to be passed the size of the tuple, the index of the element you want and the tuple.


3rd approach:

//one sequence but the macro is more complex
struct C
{
    MY_MACRO3((int,x)(float,y)(double,z))
};

This macro borrows heavily from BOOST_FUSION_ADAPT_STRUCT and similar macros.

//Heavily "inspired" from BOOST_FUSION_ADAPT_STRUCT
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_0(X, Y)  \
    ((X, Y)) CREATE_MY_MACRO_PLACEHOLDER_FILLER_1
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_1(X, Y)  \
    ((X, Y)) CREATE_MY_MACRO_PLACEHOLDER_FILLER_0
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_0_END
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_1_END

#define DECLARE_DATA_MEMBER3(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME));

#define DEFINE_ACCESSOR3(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)(){ return BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)); }

#define MY_MACRO3(TYPES_AND_NAMES) \
BOOST_PP_SEQ_FOR_EACH(DECLARE_DATA_MEMBER3,_,BOOST_PP_CAT(CREATE_MY_MACRO_PLACEHOLDER_FILLER_0 TYPES_AND_NAMES,_END)) \
public: \
BOOST_PP_SEQ_FOR_EACH(DEFINE_ACCESSOR3,_,BOOST_PP_CAT(CREATE_MY_MACRO_PLACEHOLDER_FILLER_0 TYPES_AND_NAMES,_END))

In this approach the helper macros are basically unchanged. The only (big) difference is that the sequence used in the for_each is not simply TYPES_AND_NAMES but BOOST_PP_CAT(CREATE_MY_MACRO_PLACEHOLDER_FILLER_0 TYPES_AND_NAMES,_END). This is a clever trick to force the double parentheses. It works like this:

CREATE_MY_MACRO_PLACEHOLDER_FILLER_0(int,x)(float,y)_END
    //CREATE_MY_MACRO_PLACEHOLDER_FILLER_0(A,B)->((A,B))CREATE_MY_MACRO_PLACEHOLDER_FILLER_1
((int,x))CREATE_MY_MACRO_PLACEHOLDER_FILLER_1(float,y)_END
    //CREATE_MY_MACRO_PLACEHOLDER_FILLER_1(A,B)->((A,B))CREATE_MY_MACRO_PLACEHOLDER_FILLER_0
((int,x))((float,y))CREATE_MY_MACRO_PLACEHOLDER_FILLER_0_END
    //CREATE_MY_MACRO_PLACEHOLDER_FILLER_0_END->
((int,x))((float,y))

Running on Coliru.

like image 65
3 revs Avatar answered Feb 03 '23 18:02

3 revs