2

I would like to run macros on a list of class names to avoid copy/pasting errors and hassle.

Imagine, as a simple example, that every class in my SDK needs to call a static allocation method before it is used. So, every time I add a new class, I have to manually add the following line at initialization:

MyNewClass::allocate();

And I also need to do the same for initialization and destruction.

So, instead of doing this manually every time, I was wondering if there was a way to write a list of all my class names somewhere, then define a macro to call the corresponding methods for each class in the list. Something in the lines of:

#define ALLOCATE( TheClass ) TheClass ## ::allocate();

But instead of just passing TheClass as an argument, I'd like to pass a list of my classes. So by calling:

ALLOCATE_ALL( ClassA, ClassB, ClassC )

it would expand to:

ClassA::allocate();
ClassB::allocate();
ClassC::allocate();

Ultimately, I would like to be able to define a class list and have multiple macros iterate over it. Something in the lines of:

ALLOCATE_ALL( MyClassList )
INIT_ALL( MyClassList )
DESTROY_ALL( MyClassList )

I've already taken a look at variadic macros but, if I understand the concept correctly, you have to define as many macros as the final number of arguments; and that is simply not viable in my case.

Is this possible at all?

Thanks for any help and/or feedback.

Klas Lindbäck
  • 33,105
  • 5
  • 57
  • 82
LarrxX
  • 45
  • 1
  • 7

6 Answers6

12

If you are satisfied with having one list of classes, you can use the following trick:

#define MY_CLASSES X(ClassA) X(ClassB) X(ClassC)

Then you can do something like:

#define X(a) a::allocate();
MY_CLASSES
#undef X

To do something else, you can do:

#define X(a) a::init();
MY_CLASSES
#undef X
Lindydancer
  • 25,428
  • 4
  • 49
  • 68
  • Works great! You'd get a perfect score if I didn't have to undef/redef ;) . That said, it does the job, thanks a bunch! – LarrxX Aug 22 '14 at 08:12
  • 2
    This is the [X-macros](http://stackoverflow.com/questions/6635851/real-world-use-of-x-macros) approach. If you don't like doing `#undef` afterward, you can put the contents of `MY_CLASSES` in a file (without inclusion guards) that you `#include` instead of using `MY_CLASSES`. At the end of this file, you can put `#undef X`. – jxh Aug 22 '14 at 16:11
2

You could use a variadic function template :

#include <iostream>

// Thanks to Jarod42 for improving it !
template <class... TClasses>
    void allocateAll() {
    std::initializer_list<int>{(TClasses::allocate(), void(), 0)...};
}

struct C1 { static void allocate() { std::cout << "allocated C1\n"; } };
struct C2 { static void allocate() { std::cout << "allocated C2\n"; } };
struct C3 { static void allocate() { std::cout << "allocated C3\n"; } };

int main()
{
    allocateAll<C1, C2, C3>();
    return 0;
}

Outputs :

allocated C1
allocated C2
allocated C3

The functions are called in the same order that you pass their classes in.

You can also centralize the classes list :

// Use a template instead of a function so you can typedef it
template <class... TClasses>
struct ClassesList {
    static void allocateAll() {
        std::initializer_list<int>{(TClasses::allocate(), void(), 0)...};
    }

    // Add any other utilities here

private:
    ClassesList();
};

// Declare your list
using MyClassesList = ClassesList<C1, C2, C3>;

int main(int, char**) {
    // Just as before
    MyClassesList::allocateAll();
}
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • Use `initializer_list` to have specified order (left to right). – Jarod42 Aug 21 '14 at 15:18
  • @Jarod42 absolutely. That makes it even more cryptic though. – Quentin Aug 21 '14 at 15:25
  • Not necessary: [Demo](https://ideone.com/xLLZxJ). (Note: I added stuff to support cases where `allocate()` returns object with overloaded `operator ,`). – Jarod42 Aug 21 '14 at 15:34
  • Hey, this one is cool ! May I edit it in ? Edit: also, why the leading `0` ? – Quentin Aug 21 '14 at 15:37
  • Go improve your answer :) . (leading useless `0` removed, I added it to manage empty case, but it is not necessary). – Jarod42 Aug 21 '14 at 15:46
  • I want to have one list of classes, defined in only one place. In this case, if I have `create`, `allocate` and `deallocate` methods, I'd have to write the class list once for each call of the template. – LarrxX Aug 22 '14 at 08:13
  • @LarrxX there you go ! – Quentin Aug 22 '14 at 08:32
  • Thanks for taking the time to write this elegant solution, but I finally went with Lindydancer's solution for another reason that I might have omitted in the original the post: Some of the macro work I want to do involves initializing some statick variables like `int* MyClass::_thePointer( NULL );` for all the listed classes. I don't think your solution can accomodate that, can it? – LarrxX Aug 22 '14 at 08:38
  • @LarrxX That's alright. Be sure to include _all_ of your requirements at once in your questions though, because in some cases you may invalidate answers by editig it afterwards, yielding confusion and a downvoting fest. – Quentin Aug 22 '14 at 08:42
  • @LarrxX _initializing_ is fine. But if you're talking about generating _definitions_ for these static members, then no, templates can't help you as-is. You could proxy all your static members through template instantiations though. – Quentin Aug 25 '14 at 11:15
2

To improve slightly the answer given by LindyLancer suggesting X-macros you could have a seperate file

 // file my_classes.def
 #ifndef MY_CLASS
 #error MY_CLASS should be defined before including my_classes.def
 MY_CLASS(ClassA)
 MY_CLASS(ClassB)
 /// etc...
 #undef MY_CLASS

Then you would include that several times, e.g.

 #define MY_CLASS(Classname) class Classname;
 #include "my_classes.def"

and later, inside some initializer (or your main)

 #define MY_CLASS(Classname) Classname::init();
 #include "my_classes.def"
Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
1

Using Boost.Preprocessor :

#include <boost/preprocessor/seq/for_each.hpp>

#define MyClassList \
    (ClassA) \
    (ClassB) \
    (ClassC)

#define ALLOCATE_ALL( R, DATA, ELEM ) \
    ELEM :: allocate();

#define INIT_ALL( R, DATA, ELEM ) \
    ELEM :: init();

//...

BOOST_PP_SEQ_FOR_EACH( ALLOCATE_ALL, _, MyClassList )
BOOST_PP_SEQ_FOR_EACH( INIT_ALL, _, MyClassList )
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • I don't like using Boost, for so many reason I don't want to go into now (and I don't want to turn this into a troll/flame war :) ). But thanks for taking the time and answering. – LarrxX Aug 22 '14 at 08:03
1

You can define a generic macro that iterates, but the one time definition of it is ugly. This is because you do need one macro definition for each argument, up to the max number of nesting levels supported by your compiler (I believe the minimum is at least 63, but GCC is only limited by available memory). But since it is generic, you may find other uses of it.

For up to 5, a possible implementation is:

#define M_ITER(M, ...) \
        M_ITER_(__VA_ARGS__, _5, _4, _3, _2, _1)(M, __VA_ARGS__)
#define M_ITER_(_1, _2, _3, _4, _5, X, ...) M_ITER ## X

#define M_ITER_1(M, X)      M(X)
#define M_ITER_2(M, X, ...) M(X) M_ITER_1(M, __VA_ARGS__)
#define M_ITER_3(M, X, ...) M(X) M_ITER_2(M, __VA_ARGS__)
#define M_ITER_4(M, X, ...) M(X) M_ITER_3(M, __VA_ARGS__)
#define M_ITER_5(M, X, ...) M(X) M_ITER_4(M, __VA_ARGS__)

This is basically how BOOST_PP_SEQ_FOR_EACH is implemented.

And then, you could use it for your purpose like this:

#define ALLOCATE_ALL(...) M_ITER(ALLOCATE, __VA_ARGS__)
#define INIT_ALL(...)     M_ITER(INIT, __VA_ARGS__)
#define DESTROY_ALL(...)  M_ITER(DESTROY, __VA_ARGS__)

ALLOCATE_ALL(ClassA, ClassB, ClassC)
INIT_ALL(ClassA, ClassB, ClassC)
DESTROY_ALL(ClassA, ClassB, ClassC)
jxh
  • 69,070
  • 8
  • 110
  • 193
  • This is what I'm trying to avoid, I don't want to write a variadic macro with N number of definitions. – LarrxX Aug 22 '14 at 08:14
  • You are claiming there is no upper bound to the number of classes that you would pass as arguments into the macro. If that is really the case, this is not a suitable approach. But, in practice, I find such a case very rare (theoretically unbounded use cases are actually bounded by something else, or the number changes very slowly over time, so being able to handle double the current number is sufficient). – jxh Aug 22 '14 at 15:35
  • I am not claiming there is no upper bound, I just said I don't like the idea of writing as many macros as classes. I'm about 15 classes in, and I'm about 25% of the way through my project. You can see how writing all these variadic macros can become quite cumbersome in my case. – LarrxX Aug 25 '14 at 09:33
  • That's the misunderstanding. This answer proposes you only write one such macro. – jxh Aug 25 '14 at 14:36
  • I'm really trying to understand here, but you're making it harder and harder... In the writeup to your answer you say _This is because you do need one macro definition for each argument, up to the max number of nesting levels supported by your compiler_ and show an example of 5 macros for a nested definition 5 levels deep. And when I say that's not what I'm looking for, that I don't want to write one macro per level, you reply _This answer proposes you only write one such macro._ I really really don't follow, sorry... – LarrxX Aug 26 '14 at 07:18
  • You only need one `M_ITER` macro. You need to expand its definition out to handle 60 arguments, and add the corresponding changes to the helper macros. Expanding it out is a straight forward exercise. Once you have defined `M_ITER` once, you use it over and over again. – jxh Aug 26 '14 at 07:25
1

The accepted answer by Lindydancer is unsatisfactory to me because it relies on repeatedly redefining the X macro, which seems mysterious on use and could lead to collisions.

Instead, I prefer a variation that accepts the name of the macro as a parameter:

#define FOR_EACH_CLASS(macro) \
  macro(ClassA) \
  macro(ClassB) \
  macro(ClassC)

This would be used liked like:

#define CALL_ALLOCATE(classname) classname::allocate();
FOR_EACH_CLASS(CALL_ALLOCATE)
#undef CALL_ALLOCATE   /* optional undef */

There is another answer to a related question: Real-world use of X-Macros that gives more examples of the technique. (Note that, in that question, the accepted answer once again relies on redefining a macro with a particular name, whereas the one I linked accepts the macro name as a parameter.)

Scott McPeak
  • 8,803
  • 2
  • 40
  • 79
  • I think you just convinced me that your answer is better than my original answer. (At least I plan to use this form the next time the situation calls for it.) I would recommend that you remove the `#undef` from your example, it is not needed and it will only inadvertently encourage people to use the same macro name over and over again, which is not needed with this solution. – Lindydancer Oct 28 '22 at 20:19