Search code examples
c++c++11macrosx-macros

create Macro to collect token (parameter) into a list, one by one


I am trying to create a macro that generate pointer to instance of another class to denote directional relation.

//#define BIND(A,B) ?
//can be modified a little, top header
BIND(CAT,DOG)
BIND(CAT,TREE)
BIND(CAT,RAT)
BIND(DOG,TREE)
//#define MACRO_CAT (need?) ? 
//#define MACRO_DOG (need?) ?

enter image description here

Above is the related diagram. (In real case, there are 100+ classes.)
Arrow head (red) is Right<>. Arrow tail (green) is Left<>. (below snippet)

Is it possible that the above code will create macro MACRO_CAT/MACRO_DOG automatically like this? :-

//v should not be modified
class Cat{
    MACRO_CAT
    /* expand to :-
        Right<Dog> dogs;
        Right<Tree> trees;
        Right<Rat> rats;
    */
};
class Dog{
    MACRO_DOG
    /* expand to :-
        Right<Tree> trees;
        Left<Cat> cats;
    */
};

This hackery macro magic would be really useful for maintaining relation between objects with ultimate performance.

I guess X-macro is a possible solution, but I have relatively low experience about it.
I have also read :-

Just a rough guide/idea is appreciated. (full code is not need, but I don't mind)

Edit: In real case, the BIND scatters in many headers.
This is an example of #include flow (lower #include upper):- enter image description here


Solution

  • I've leaned towards TMP rather than macros in this solution.

    The first step is to gather all of the declared bindings. It might be feasible to allow declaring bindings freely and scatter them through other code. It would, however, require some way to keep and update the state of the list, which is pretty much the most arcane thing you would want to do in C++. So let's not do that.

    The binding files will have to contain only preprocessor instructions, and calls to the macro BIND, as follows:

    BIND(Cat, Dog)
    BIND(Cat, Tree)
    BIND(Cat, Rat)
    BIND(Dog, Tree)
    

    In other words, the preprocessed files must only contain the substituted output from BIND. Then we can sandwich these between bindTop.h and bindBottom.h:

    template <class...>
    struct pack;
    
    // Stuff in bindTop.h
    
    #define BIND(T, U) \
        pack<struct T, struct U>,
    
    using AllBindings = pack<
    
    // End of bindTop.h, beginning of the binding file(s)
    
        BIND(Cat, Dog)
        BIND(Cat, Tree)
        BIND(Cat, Rat)
        BIND(Dog, Tree)
    
    // End of the binding file(s), beginning of bindBottom.h
    
        void // Pairs up with the last comma,
             // will be silently ignored in further processing
    >;
    
    #undef BIND
    
    // Stuff in bindBottom.h
    

    Now we have our list of bindings inside AllBindings.

    Next step: how do we inject a member into a class? I've ditched the macro, and used member inheritance instead. So a class definition like:

    struct Cat  : WithBindings<Cat,  AllBindings> { };
    

    ... will end up inheriting from several structs which will define the members Right<Dog> dogs, Right<Tree> trees, and Right<Rat> rats, and thus will be able to access them almost as if they were its.

    But how to declare that the member of type Right<Dog> must be called dogs? Macros, of course! Let's make empty templates for left- and right-making base classes:

    template <class T, class Binding>
    struct MakeLeftMember { };
    
    template <class T, class Binding>
    struct MakeRightMember { };
    

    And then we'll use a macro to specialize these for each of our classes, with the name of the class and the name of the corresponding members:

    #define BINDING_MEMBER_NAME(type_, memberName_) \
        template <class T> struct MakeLeftMember<T, pack<type_, T>> { \
            Left<type_> memberName_; \
        }; \
        template <class T> struct MakeRightMember<T, pack<T, type_>> { \
            Right<type_> memberName_; \
        }
    

    Binding is expected to be one of the pack<L, R> that we defined with BIND. Now instantiating e.g. MakeLeftMember<T, pack<L, R>> will dispatch to the specialization only if T is R, that is, the binding is indeed a left-binding for T. Then the specialization will generate the suitably-named Left<L> member to be inherited by T. In other cases, the base template is selected, and nothing happens.

    The last missing link is of course WithBindings<T, AllBindings>, which simply dispatches all of the bindings into the member makers and inherits the resulting generated members:

    template <class T, class... Bindings>
    struct WithBindings<T, pack<Bindings...>>
    : MakeLeftMember <T, Bindings>...
    , MakeRightMember<T, Bindings>... { };
    

    And there we go. See it live on Coliru!