在 C++ 中使用 X 宏时处理尾随逗号的最佳方法

Best way to deal with a trailing comma when using X macros in C++

在使用 X 宏时处理多余尾随逗号的最佳方法是什么?具体来说,我在文件 test01.cpp

中有以下设置
struct Foo {
    #define X(name,val) int name;
    #include "test01.def"
    #undef X

    Foo() :
        #define X(name,val) name(val),
        #include "test01.def"
        #undef X
    {}
};

int main(){
    Foo foo;
}

test01.def,我有

X(foo,1)
X(bar,23)

由于错误而无法编译

test01.cpp: In constructor 'Foo::Foo()':
test01.cpp:10:5: error: expected identifier before '{' token
     {}

基本上,在成员初始值设定项列表中的最后一个元素之后有一个尾随逗号。现在,我们可以通过添加一个虚拟变量来解决这个问题:

struct Foo {
private:
    void * end;
public:
    #define X(name,val) int name;
    #include "test01.def"
    #undef X

    Foo() :
        #define X(name,val) name(val),
        #include "test01.def"
        #undef X
        end(nullptr)
    {}
};

int main(){
    Foo foo;
}

然而,这有点丑陋。因此,有没有更好的方法来处理成员初始值设定项列表中的尾随逗号?


编辑 1

这是另一个仍然有点丑陋的选项:

struct Foo {
    #define X(name,val) int name;
    #include "test01.def"
    #undef X

    Foo() :
        #define X(name,val) name(val),
        #define XLAST(name,val) name(val)
        #include "test01.def"
        #undef XLAST
        #undef X
    {}
};

int main(){
    Foo foo;
}

连同

#ifndef XLAST
    #define XLAST X
    #define CLEANUP
#endif

X(foo,1) 
XLAST(bar,23)

#ifdef CLEANUP
    #undef XLAST
    #undef CLEANUP
#endif

基本上,我们定义宏XLAST来处理最后的逗号。如果我们使用 XLAST,我们必须像 X 一样手动取消定义它,但如果我们没有明确定义它,我们会自动执行此操作。

既然你已经标记了这个 C++14,解决这个问题的最简单方法是使用 brace-or-equal-initializers 而不是 mem-initializers。

struct Foo {
    #define X(name,val) int name = val;
    #include "test01.def"
    #undef X

    // Foo() {} // uncomment if you do not want Foo to be an aggregate
};

如果您想坚持使用预处理器解决方案,可以使用 Boost.Preprocessor to do this. You'll need to change the format of your data member definitions so it forms a sequence

#define FOO_MEMBERS ((int,i,10)) ((long,j,20))

我也添加了指定任意数据类型的功能。

首先让我们声明并初始化这些数据成员

struct Foo
{
    #define OP(s, data, elem) BOOST_PP_TUPLE_ELEM(3, 0, elem) \
                              BOOST_PP_TUPLE_ELEM(3, 1, elem) = \
                              BOOST_PP_TUPLE_ELEM(3, 2, elem);
    BOOST_PP_SEQ_FOR_EACH(OP, , FOO_MEMBERS)
    // expands to 
    //      int i = 10; long j = 20;
    #undef OP

    Foo() = default;  // default constructor
};

BOOST_PP_SEQ_FOR_EACH 将为序列 FOO_MEMBERS.

中的每个元素扩展宏 OP

BOOST_PP_TUPLE_ELEM 只是从其 元组 参数中提取单个元素。

接下来,让我们给Foo一个构造函数,它接受与每个数据成员对应的参数并对其进行初始化。

#define OP(s, data, elem) (BOOST_PP_TUPLE_ELEM(3, 0, elem) BOOST_PP_TUPLE_ELEM(3, 1, elem))
Foo(
    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(OP, , FOO_MEMBERS))
    // expands to
    //      int i, long j
) :
#undef OP

#define OP(s, data, elem) (BOOST_PP_TUPLE_ELEM(3, 1, elem)(BOOST_PP_TUPLE_ELEM(3, 1, elem)))
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(OP, , FOO_MEMBERS))
// expands to
//      i(i), j(j)
#undef OP
{}

我们使用 BOOST_PP_SEQ_ENUMBOOST_PP_SEQ_FOR_EACH.

扩展的结果生成一个逗号分隔的列表

Live demo

如果您将 end 声明为 std::nullptr_t 类型,编译器可能会对其进行优化并将其删除。 reader.

的意图很明确
 struct Foo {
private:
 static std::nullptr_t end;

或者,您可以声明 char end[0];(这不会使用任何 space),但一些编译器可能会拒绝。

当然,如,您可以使用一些 X-macro 友好的结构来代替。