在类名列表上迭代预处理器宏

Iterate a preprocessor macro on a list of class names

本文关键字:预处理 处理器 迭代 列表      更新时间:2023-10-16

我想在类名列表上运行宏,以避免复制/粘贴错误和麻烦。

想象一下,作为一个简单的例子,我的SDK中的每个类在使用之前都需要调用一个静态分配方法。因此,每次添加新类时,都必须在初始化时手动添加以下行:

MyNewClass::allocate();

对于初始化和销毁,我也需要做同样的事情。

所以,而不是每次手工这样做,我想知道是否有一种方法来编写一个列表的所有我的类名的地方,然后定义一个宏调用相应的方法在列表中的每个类。

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

但是不只是传递TheClass作为参数,我想传递我的类列表。通过调用:

ALLOCATE_ALL( ClassA, ClassB, ClassC )

将展开为:

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

最终,我希望能够定义一个类列表,并有多个宏迭代它。

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

我已经看了一下可变宏,但是,如果我正确理解了这个概念,你必须定义和最终参数数量一样多的宏;这在我的情况下是不可行的。

这可能吗?

感谢您的帮助和/或反馈。

如果您对只有一个类列表感到满意,您可以使用以下技巧:

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

然后你可以这样做:

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

要做其他事情,你可以这样做:

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

您可以使用可变函数模板:

#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 C1n"; } };
struct C2 { static void allocate() { std::cout << "allocated C2n"; } };
struct C3 { static void allocate() { std::cout << "allocated C3n"; } };
int main()
{
    allocateAll<C1, C2, C3>();
    return 0;
}

输出:

allocated C1
allocated C2
allocated C3

调用函数的顺序与传递类的顺序相同。

你也可以集中类列表:

// 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();
}

要稍微改进LindyLancer建议的x -宏的答案,您可以有一个单独的文件

 // 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

那么你要把包含几次,例如

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

之后,在一些初始化器(或您的main)中

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

使用Boost。预处理:

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

您可以定义一个迭代的泛型宏,但是它的一次性定义是丑陋的。这是因为您确实需要为每个参数定义一个宏,直至编译器支持的最大嵌套级别(我认为最小值至少为63,但GCC仅受可用内存的限制)。但由于它是通用的,你可能会发现它的其他用途。

对于最多5个,可能的实现是:

#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__)

这就是BOOST_PP_SEQ_FOR_EACH的基本实现方式。

然后,你可以像这样使用它:

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

Lindydancer接受的答案对我来说是不满意的,因为它依赖于反复重新定义X宏,这在使用上似乎很神秘,可能导致碰撞。

相反,我更喜欢接受宏的名称作为参数的变体:

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

可以这样使用:

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

有一个相关问题的另一个答案:X-Macros的实际使用,提供了更多的技术示例。(请注意,在该问题中,可接受的答案再次依赖于使用特定名称重新定义宏,而我链接的那个宏接受宏名称作为参数。)