1

I have a struct in my server that I would like to generate it with macro or template in C++ as it has a lot of redundant things:

struct MyBlock {
  void Merge(const MyBlock& from) {
    if (apple.HasData()) {
      apple.Merge(from.apple);
    }
    if (banana.HasData()) {
      banana.Merge(from.banana());
    }
    ...
  }

  void Clear() {
    apple.Clear();
    banana.Clear();
    ...
  }

  void Update(const SimpleBlock& simple_block) {
    if (simple_block.apple.Updated()) {
      apple.Add(simple_block.apple);
    }
    if (simple_block.banana.Updated()) {
      banana.Add(simple_block.banana);
    }
    ...
  }
  Fruit apple;
  Fruit banana;
  Animal dog;
  Animal cat;
  ...
}

struct SimpleBlock {
  SimpleFruit apple;
  SimpleFruit banana;
  SimpleAnimal dog;
  SimpleAnimal cat;
  ...;
}

I would like to define more variables in the two blocks like apple and dog. I would also like to define more pairs of such blocks. But it involves a lot of trivial work. So my question is how we can use macro, template or some other C++ features including C++11 to generate these blocks in compile time?

The reason why I don't use collections to store those variable is because MyBlock struct would be passed as a parameter in another template class which would dynamically allocate and release this block in run time. It is actually a thread local block that would be aggregated periodically.

3 Answers 3

2

Straightforward enough with preprocessor list iteration:

#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B
#define M_ID(...) __VA_ARGS__

#define M_LEFT(L, R) L
#define M_RIGHT(L, R) R

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)

#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)
//.. extend this to higher numbers with some copy&paste


#define MYBLOCK(...) struct MyBlock { \
  void Merge(const MyBlock& from) { \
      M_FOR_EACH(BLOCK_MERGE, __VA_ARGS__) \
  } \
  void Clear() { \
      M_FOR_EACH(BLOCK_CLEAR, __VA_ARGS__) \
  } \
  void Update(const SimpleBlock& simple_block) { \
      M_FOR_EACH(BLOCK_UPDATE, __VA_ARGS__) \
  } \
  M_FOR_EACH(BLOCK_FIELD, __VA_ARGS__) \
}

#define BLOCK_MERGE(F) if (M_ID(M_RIGHT F).HasData()) { \
  M_ID(M_RIGHT F).Merge(from.M_ID(M_RIGHT F)); \
}
#define BLOCK_CLEAR(F) M_ID(M_RIGHT F).Clear;
#define BLOCK_UPDATE(F) if (simple_block.M_ID(M_RIGHT F).Updated()) { \
  M_ID(M_RIGHT F).Add(simple_block.M_ID(M_RIGHT F)); \
}
#define BLOCK_FIELD(F) M_ID(M_LEFT F) M_ID(M_RIGHT F);


#define SIMPLEBLOCK(...) struct SimpleBlock { M_FOR_EACH(SIMPLE_DECL, __VA_ARGS__) }
#define SIMPLE_DECL(F) M_CONC(Simple, M_ID(M_LEFT F)) M_ID(M_RIGHT F);

#define FIELDS (Fruit, apple),(Fruit,banana),(Animal,dog),(Animal,cat)

MYBLOCK(FIELDS);
SIMPLEBLOCK(FIELDS);

Add the necessary further member variables to FIELDS in the existing format, and they will be added to the structs emitted by MYBLOCK and SIMPLEBLOCK. (Remember to extend M_FOR_EACH with more iterations... easy to to with a few ctrl+c,ctrl+v.)

Sign up to request clarification or add additional context in comments.

3 Comments

Very elegant! Thank you. Could you explain what is "M_ID(M_RIGHT F)"?
@Fake F ends up containing the (type,name) pairs; M_RIGHT is a macro that takes two arguments and returns the one on the right. M_ID is an identity macro (does nothing). F is already in the right syntax to form the whole argument list to M_RIGHT, so all M_ID does is force the name+argument list, placed next to each other, through an extra expansion pass, to make sure it gets recognised and expanded before the calling macro returns.
@Leushenko Thank you for your reply. So the M_ID(M_RIGHT F) would be expanded to M_RIGHT(F). Got it! I actually need something more than this and I asked another question: stackoverflow.com/questions/26582302/… . I would appreciate if you take a look. Thank you!
1
template <typename SimpleT>
class BlockTemplate
{
public:
void Merge(const BlockTemplate& from) {
    if (HasData()) {
      Merge(from.simpleData);
    }
}

void Update(const SimpleT& simple_block) {
    if (simple_block.Updated()) {
      Add(simple_block.data);
    }
}

protected:
SimpleT simpleData;
};

Now, you can create objects of type BlockTemplate<SimpleFruit>, BlockTemplate<SimpleAnimal> etc. You could also store pointers to all these BlockTemplate objects in a container after having BlockTemplate inherit from an abstract type. Or, better yet, use the new-fangled type-erasure methods - boost::type_erasure::any for example.

EDIT : If you don't want to use the container that way, you could also make BlockTemplate variadic and store a tuple of different(type-wise) SimpleT objects and modify the Merge and Update functions accordingly. The problem with this is that it becomes much harder to track your SimpleT objects - std::tuple doesn't allow you to give names. You would be referring to the values as get<N>(tupleData).

Comments

0

The description of why you don't use a collection sounds like some optimization thing. Have you measured?

Anyway, one simple solution is store pointers to the objects in a collection.

Then you can iterate over the collection.

2 Comments

But I still have to hardcode these pointers into a collection right? The pointers must be there in compile time, otherwise how I can pass the block into a template argument? On the other hand, I still have two similar blocks that all similar variables in the two blocks have to be defined twice.
There's nothing special about arguments whose types are templated. But with self-pointers in the picture you do need to take charge of copying. Either disallow it, or support it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.