0

I'm attempting to generate some code with preprocessor. In some class, I need to define multiple variable member, their corresponding setters and a map containing reference on each declared variable.

To illustrate my need, you can find the following code example of what I want to achieve. In this example, I only declare two variable but in the real case more variable should be declared in different class:

#include <boost/preprocessor.hpp>

#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>

class typeBase {
};

template <typename T>
class typeBase_: public typeBase {
    private:
        T value_;
    public:
        typeBase_() { }
        typeBase_(const T& v): value_(v) { }
};

using typeInt = typeBase_<int>;
using typeStr = typeBase_<std::string>;

std::unordered_set<std::string> changed_list_;

//////////////////////////////////////////////////////////
// Here use generation to generate the bellow code
//////////////////////////////////////////////////////////
typeInt mInt_;
typeStr mStr_;

std::unordered_map<std::string, typeBase&> properties_ = {
    {"mInt", mInt_},
    {"mStr", mStr_}
};

void set_mInt(const typeInt& mInt) {
    mInt_ = mInt;
    changed_list_.insert("mInt");
}

void set_mStr(const typeStr& mStr) {
    mStr_ = mStr;
    changed_list_.insert("mStr");
}
/////////////////////////////////////////////
/////////////////////////////////////////////

int main()
{
    set_mInt(2);
    set_mStr(std::string("test"));
}

At the time I first tried with Boost preprocessing library and I am stuck for the moment at creating the map containing reference to each variable member:

#define MODEL_DECLARE(...)                                                       \
  std::unordered_map<std::string, typeBase&> properties = {                      \
        MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))          \
  };

#define MODEL_GENERATE_MAP_ITEMS(Args)                                           \
  BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_GENERATE_MAP_ITEM, %%, Args))

#define MODEL_GENERATE_MAP_ITEM(s, Unused, Arg)                                  \
  {(MODEL_STRINGIFY(BOOST_PP_TUPLE_ELEM(2, 0, Arg)), BOOST_PP_TUPLE_ELEM(2, 0, Arg))}

#define MODEL_STRINGIFY_(V) #V
#define MODEL_STRINGIFY(V) MODEL_STRINGIFY_(V)

#define MODEL_MAKE_ITEM(s, Unused, Arg)                                          \
  {BOOST_PP_TUPLE_ELEM(2, 0, Arg) BOOST_PP_TUPLE_ELEM(2, 1, Arg)}

// Generate model with this line
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))

With this code I produce this preprocessing line:

std::unordered_map<std::string, typeBase&> properties = { {("mInt", mInt)}, {("mStr", mStr)} };

As you can see, I have parenthesis which need to be removed, which I barrely failed to to.

Do you know a better solution to achieve what I need, or how can I FIX my code to successfully generate the needed code?

Regards

EDIT1:

I started to implement the @parktomatomi solution and I also tryed to add code to declare variable and setters:

#include <boost/preprocessor.hpp>

#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <cassert>

class typeBase {
};

template <typename T>
class typeBase_: public typeBase {
    private:
        T value_;
    public:
        typeBase_() { }
        typeBase_(const T& v): value_(v) { }
};

using typeInt = typeBase_<int>;
using typeStr = typeBase_<std::string>;

std::unordered_set<std::string> changed_list_;

//////////////////////////////////////////////////////////
// Here use generation to generate the bellow code
//////////////////////////////////////////////////////////   
template <typename... Ts>
std::unordered_map<std::string, typeBase*> build_properties(Ts&&... args) { 
    return std::unordered_map<std::string, typeBase*> { { args.first, args.second }... };
}

// Macro used to generate properties map
#define MODEL_GENERATE_MAP_ITEM(Name, Type)          std::make_pair( #Name, &Name##_ )
#define MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg
#define MODEL_GENERATE_MAP_ITEMS(Args)               BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_MAP_ITEM,,Args))

// Macro used to declare vars and setters
#define MODEL_GENERATE_VAR(Name, Type)              Type Name##_;                           \
                                                    void set_##Name(const Type& Name) {     \
                                                        Name##_ = Name;                     \
                                                        changed_list_.insert(#Name);        \
                                                    };
#define MODEL_UNWRAP_VAR(Unused1, Unused2, Arg)     MODEL_GENERATE_VAR Arg
#define MODEL_GENERATE_VARS(Args)                   BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_VAR,,Args)

// Macro to generate model
#define MODEL_DECLARE(...)                                                       \
  std::unordered_map<std::string, typeBase*> properties_ = build_properties(     \
        MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))          \
  );                                                                             \
  MODEL_GENERATE_VARS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

// Generate model
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))

int main() {
    assert(properties_.size() == 2);
    assert(properties_["mInt"] == &mInt_);
    assert(properties_["mStr"] == &mStr_);
}

However this do not compile because the preprocessing generation add parenthesis around the declaration:

(typeInt mInt_; void set_mInt(const typeInt& mInt) { mInt_ = mInt; changed_list_.insert("mInt"); };) (typeStr mStr_; void set_mStr(const typeStr& mStr) { mStr_ = mStr; changed_list_.insert("mStr"); };)

How to remove this paranthesis?

xamix
  • 13
  • 5

1 Answers1

0

I ran into two issues trying to get this to compile:

  1. You can't have a container of references
  2. Macros and braces don't mix well

To fix #1, use a pointer instead:

std::unordered_map<std::string, typeBase*>

To fix #2, use a helper function to initialize the map:

template <typename... Ts>
std::unordered_map<std::string, typeBase*> build_properties(Ts&&... args) { 
    return std::unordered_map<std::string, typeBase*> { { args.first, args.second }... };
}

Then the goal becomes to generate this with macros:

build_properties(
    std::make_pair("mInt", &mInt_),
    std::make_pair("mStr", &mStr_)
)

Which is a bit easier and compiles successfully:

#define MODEL_GENERATE_MAP_ITEM(Name, Type) std::make_pair( #Name, &Name##_ )

#define MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg

#define MODEL_GENERATE_MAP_ITEMS(Args)                                           \
  BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_MAP_ITEM,,Args))

#define MODEL_DECLARE(...)                                                       \
  std::unordered_map<std::string, typeBase*> properties = build_properties(      \
        MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))          \
  );

// Generate model with this line
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))

More directly related to your question, the BOOST_PP_VARIADIC_TO_SEQ macro adds a pair of parenthesis around your arguments:

(mInt, typeInt), (mStr, typeStr) ==> ((mInt, typeInt)) ((mStr, typeStr))

So when BOOST_PP_SEQ_TRANSFORM generates its macros, the argument generated for each transformation macro has parenthesis around it, e.g. (mInt, typeInt). To get rid of those parenthesis, I added this macro:

MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg

which when Arg is replaced:

MODEL_GENERATE_MAP_ITEM (mInt, typeInt)

which gets transformed one last time to:

std::make_pair( "mInt", mInt_ )

Demo: https://godbolt.org/z/5fyo3N


Update

To get the updated code working, I had to make 3 changes:

  1. Instead of BOOST_PP_SEQ_TRANSFORM, use BOOST_PP_SEQ_FOR_EACH. The former does a map on each item and turns it back into a SEQ:
    BOOST_PP_SEQ_TRANSFORM(ADD_1, , (a)(b)) ==> (a+1)(b+1)
    
    While BOOST_PP_SEQ_FOR_EACH does a mapping operation, but does not add the parenthesis back:
    BOOST_PP_SEQ_TRANSFORM(ADD_1, , (a)(b)) ==> a+1 b+1
    
  2. Since you've got that set_mInt function, you've got to add an assignment operator to typeBase_<T>:

    typeBase_& operator =(const typeBase_& that) { value_ = that.value_; return *this; }
    
  3. MODEL_GENERATE_VARS needs to be before the map declaration so mInt_ and friends are visible

All together:

#define MODEL_GENERATE_MAP_ITEM(Name, Type)          std::make_pair( #Name, &Name##_ )
#define MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg
#define MODEL_GENERATE_MAP_ITEMS(Args)               BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_MAP_ITEM,,Args))

// Macro used to declare vars and setters
#define MODEL_GENERATE_VAR(Name, Type)              Type Name##_;                           \
                                                    void set_##Name(const Type& Name) {     \
                                                        Name##_ = Name;                     \
                                                        changed_list_.insert(#Name);        \
                                                    };
#define MODEL_UNWRAP_VAR(Unused1, Unused2, Arg)     MODEL_GENERATE_VAR Arg
#define MODEL_GENERATE_VARS(Args)                   BOOST_PP_SEQ_FOR_EACH(MODEL_UNWRAP_VAR,,Args)

// Macro to generate model
#define MODEL_DECLARE(...)                                                       \
  MODEL_GENERATE_VARS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))                     \
  std::unordered_map<std::string, typeBase*> properties_ = build_properties(     \
        MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))          \
  );

// Generate model
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))

Demo: https://godbolt.org/z/bDDe94

parktomatomi
  • 3,851
  • 1
  • 14
  • 18
  • Thank you for your help, for #1 you are right, just to be complete, it should be doable by using [std::reference_wrapper](https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper) but in my case I will go with bare pointer. I will try your solution and also generate the rest of the setter I need. – xamix Jan 10 '20 at 14:22
  • Can you explain the line `#define MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg` what does it do since macro `MODEL_GENERATE_MAP_ITEM` is not called with parenthesis? – xamix Jan 10 '20 at 14:37
  • Sure, updated the answer with more information. I didn't use `std::reference_wrapper` because I'm scared of it :) . – parktomatomi Jan 10 '20 at 15:38
  • Thank you very very much for the precision! It makes sense to me now! If I may ask to you, can you take a look at my edited question, I think there is also a trick to remove my extra parenthesis around declaration of variable an setters and for the moment I'm stuck with it. – xamix Jan 10 '20 at 15:54
  • Thank you for your update, You were a few minutes ahead of me, I just found the solution which is very similar to yours (use `BOOST_PP_SEQ_FOR_EACH`) anyway it was good to me to learn that and thank you for all your help – xamix Jan 10 '20 at 18:06