Search code examples
c++classmacrospreprocessor

Declare a class and member variables using macros in C++


I would like to use preprocessor macros to declare many classes like the following:

class ClassA : public ClassBase
{
public:
    int a;
    float b;
    char c;

    std::vector<void *> fun()
    {
        /*
         Code that uses all member variables
        */
        std::vector<void *> v{&a, &b, &c};
        return v;
    }
};

For example, the same class declared with macros may look something like this:

BEGIN_CLASS(ClassA)
    MEMBER(int, a)
    MEMBER(float, b)
    MEMBER(char, c)
END_CLASS(ClassA)

or (@Peter, thanks for ruling out above option)

NEW_CLASS(ClassA, int, a, float, b, char, c)

The only parts of the declaration that will change are class name, member variable names, member variable type and number of member variables. Everything else will follow the same template.

In my application, users will need to declare classes like this regularly and I would like to provide a simpler interface for them.

Regardless of whether this is good practice, I'd like to know if declaring a class like this is possible and if so, how?


Solution

  • #define NEW_CLASS(name_, seq_) \
        class name_ : public ClassBase \
        { \
          public: \
            IMPL_NEW_CLASS_end(IMPL_NEW_CLASS_decl_loop_a seq_)\
            \
            std::vector<void *> fun() \
            { \
                return { IMPL_NEW_CLASS_end(IMPL_NEW_CLASS_list_loop_a seq_) }; \
            } \
        };
    
    #define IMPL_NEW_CLASS_end(...) IMPL_NEW_CLASS_end_(__VA_ARGS__)
    #define IMPL_NEW_CLASS_end_(...) __VA_ARGS__##_end
    
    #define IMPL_NEW_CLASS_decl_loop_a(...) ::std::type_identity_t<__VA_ARGS__> IMPL_NEW_CLASS_decl_loop_b
    #define IMPL_NEW_CLASS_decl_loop_b(name_) name_; IMPL_NEW_CLASS_decl_loop_a
    #define IMPL_NEW_CLASS_decl_loop_a_end
    
    #define IMPL_NEW_CLASS_list_loop_a(...) IMPL_NEW_CLASS_list_loop_b
    #define IMPL_NEW_CLASS_list_loop_b(name_) &name_, IMPL_NEW_CLASS_list_loop_a
    #define IMPL_NEW_CLASS_list_loop_a_end
    
    
    NEW_CLASS(ClassA, (int)(a) (float)(b) (char)(c))
    

    I've used the (a)(b)(c) syntax for lists, because AFAIK only those lists can be traversed without generating a bunch of repetitive boilerplate macros. (can't do that with a, b, c)

    I've wrapped the type in std::type_identity_t<...> to allow arrays, function pointers, etc (int[4] x; is invalid, but std::type_identity_t<int[4]> x; is ok).

    I chose this specific syntax because the types can contain commas, so e.g. (type,name)(type,name) wouldn't be viable, because it's tricky to extract the last element from a comma-separated list (consider (std::map<int,float>,x), which counts as a comma-separated list with 3 elements).

    (name,type)(name,type), on the other hand, would be viable (extracting the first element of a list is simple), but it doesn't look as good. If you go for this syntax, note that the loops still have to use at least two macros each (_a and _b in my example), even if the two would be the same (a single-macro loop doesn't work because of the ban on recursive macros). The loops would also need two _end macros each, not one.