Home > database >  Declare a class and member variables using macros in C
Declare a class and member variables using macros in C

Time:06-05

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

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?

CodePudding user response:

#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.

CodePudding user response:

I managed to find a solution by following this post: Variadic macros tricks

Here's what I did.

STEP1: Define macro templates. One for all the number of member variables you will to support

#define _NEW_CLASS_1(name, t1, v1)      \
    struct name : public ClassBase{     \
        t1 v1;                          \
        std::vector<void *> fun(){      \
            std::vector<void *> v{&v1}; \
            return v;                   \
        }                               \
    };

#define _NEW_CLASS_2(name, t1, v1, t2, v2)   \
    struct name : public ClassBase{          \
        t1 v1;                               \
        t2 v2;                               \
        std::vector<void *> fun(){           \
            std::vector<void *> v{&v1, &v2}; \
            return v;                        \
        }                                    \
    };

#define _NEW_CLASS_3(name, t1, v1, t2, v2, t3, v3) \
    struct name : public ClassBase{                \
        t1 v1;                                     \
        t2 v2;                                     \
        t3 v3;                                     \
        std::vector<void *> fun(){                 \
            std::vector<void *> v{&v1, &v2, &v3};  \
            return v;                              \
        }                                          \
    };

// define more if you need to support more member variable

STEP2: Use variadic macros to select one of the above macros to build the class. Add more arguments following the pattern to support more than member variables.


#define _GET_OVERRIDE(cls, t1, v1, t2, v2, t3, v3, t4, v4, NAME, ...) NAME
#define NEW_CLASS(...) _GET_OVERRIDE(__VA_ARGS__, _NEW_CLASS_4, _, _NEW_CLASS_3, _, _NEW_CLASS_2, _, _NEW_CLASS_1, _)(__VA_ARGS__)

STEP3: Declare classes using NEW_CLASS macro.


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

NEW_CLASS(ClassB,
          std::string, str,
          int, n,
          double, d)

NEW_CLASS(ClassC,
          double, c)

int main()
{

    ClassA objA;
    ClassB objB;
    ClassC objC;

    return 0;
}
  • Related