1

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

Community
  • 1
  • 1
javaLover
  • 6,039
  • 2
  • 14
  • 57
  • 1
    Hm, I could certainly tell you what our hiring manager would say to a prospective C++11 candidate who proposed such "magic"... – Kerrek SB Apr 18 '17 at 09:18
  • @Kerrek SB Is it too sinister? Is it so black that I should not use at all? .... I promise I will not use it often (a few places in 25,000+ lines), and I will chant whenever I uses it. – javaLover Apr 18 '17 at 09:21
  • Are all of the bindings always grouped together? – Quentin Apr 18 '17 at 09:24
  • @Quentin Most of the time, yes. Sometimes, they may scatter around many header. But every such header always come at top-level (first thing in the `#include`), i.e. never after `class Cat/Dog{}`. I can create a *joint header* that is always included after all `BIND` and always before `class Cat/Dog{}`, if that make answering easier. – javaLover Apr 18 '17 at 09:30
  • You seem to be inclined to use ugly compile-time stuff with macros on the assumption that this will somehow magically improve performance - this is almost certainly misguided - premature optimisation is almost always a bad idea. – Paul R Apr 18 '17 at 10:19
  • @Paul R I am trying to change the existing premature optimization to something more refactor-able. I can't afford hash_map (I profiled), so I don't have much choice. I want to navigate every possible approaches, and I am also curious about this feature personally. – javaLover Apr 18 '17 at 10:23
  • @javaLover if the various `bind*.h` files can be made in such a way that after preprocessing you end up with something like `MAGIC_BEGIN() BIND(Cat, Dog) BIND(Cat, Rat) ... MAGIC_END()` in a single place, that would simplify things a great deal. Is that a reasonable assumption? – Quentin Apr 18 '17 at 11:08
  • @Quentin I believe it is reasonable. – javaLover Apr 18 '17 at 11:21
  • @Quentin When you asked, I was mainly afraid that you will group into a single bind like `BIND(a,b,c,d,e,...)`. – javaLover Apr 18 '17 at 11:27
  • 2
    You might be interested in [building-and-accessing-a-list-of-types-at-compile-time](http://stackoverflow.com/questions/18701798/building-and-accessing-a-list-of-types-at-compile-time). so you may build a list of pairs of types at compile-type and then have something like `std::tuple, Right, Right> data;`. – Jarod42 Apr 18 '17 at 11:40
  • 2
    @javaLover [here](http://coliru.stacked-crooked.com/a/a4a04281d77b5a1e) is a proof-of-concept. The types do have to be declared before (or while) the bindings are listed (that's done by using `struct` inside `BIND` here). Does that fit the bill? – Quentin Apr 18 '17 at 14:17
  • @Quentin What the magic! I am a bit disappointed that there is `#include` inside `<>`, because it limits the usage in some cases. I have to move code that is not-related to `BIND()` outside the header. But this is better than I expected!, and I should be satisfied enough to accept it. ....... – javaLover Apr 19 '17 at 02:28

1 Answers1

1

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!

Quentin
  • 58,778
  • 7
  • 120
  • 175