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.

yuefengz
  • 3,338
  • 1
  • 17
  • 24

3 Answers3

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

Alex Celeste
  • 12,824
  • 10
  • 46
  • 89
  • Very elegant! Thank you. Could you explain what is "M_ID(M_RIGHT F)"? – yuefengz Oct 27 '14 at 05:52
  • @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. – Alex Celeste Oct 27 '14 at 06:50
  • @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: http://stackoverflow.com/questions/26582302/a-group-of-variadic-macros?noredirect=1#comment41780893_26582302 . I would appreciate if you take a look. Thank you! – yuefengz Oct 27 '14 at 06:57
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).

Pradhan
  • 16,391
  • 3
  • 44
  • 59
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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 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. – yuefengz Oct 26 '14 at 01:06
  • 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. – Cheers and hth. - Alf Oct 26 '14 at 01:10