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

col17t5w  于 2023-01-15  发布在  其他
关注(0)|答案(3)|浏览(136)

在使用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一样手动取消定义它,但是在我们没有明确定义它的情况下,我们会自动取消定义。

vdzxcuhz

vdzxcuhz1#

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

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来完成,你需要改变你的数据成员定义的格式,使它形成一个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
};

X1 E2 F1 X将为序列X1 M2 N1 X中的每个元素扩展宏X1 M1 N1 X。
BOOST_PP_TUPLE_ELEM只是从它的 tuple 参数中提取单个元素。
接下来,让我们为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

fcwjkofz

fcwjkofz2#

如果你声明endstd::nullptr_t类型,编译器很可能会优化并删除它,读者也很清楚其意图。

struct Foo {
private:
 static std::nullptr_t end;

或者,您可以声明char end[0];(这不会占用任何空间),但某些编译器可能会拒绝它。
当然,作为Pratorian answered,您可以使用一些X-macro友好的构造来代替。

2izufjch

2izufjch3#

对于任何寻求不涉及BOOST_PP依赖的解决方案的人来说,一个基于预处理器的替代方案(对普通老C也很有用)是使用预处理器处理 * leading * 逗号,这比处理 * trailing * 逗号容易得多。

/// Indirection macro, so that macro expansion of parameters is done before token pasting.
#define REMOVE_FIRST(...) REMOVE_FIRST_SUB(__VA_ARGS__)

/// Paste all parameters but first.
#define REMOVE_FIRST_SUB(X, ...) __VA_ARGS__

#define FOO_DEF(X) \
  X(foo,1) \
  X(bar,23)

struct Foo {
    #define X(name,val) int name;
    FOO_DEF(X)
    #undef X

    Foo() :
        #define X(name,val) ,name(val)
        REMOVE_FIRST(FOO_DEF(X))
        #undef X
    {}
};

int main(){
    Foo foo;
}

测试:

$ gcc -E test.c
# 0 "test.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "test.c"
# 11 "test.c"
struct Foo {

    int foo; int bar;

    Foo() :

        foo(1) ,bar(23)

    {}
};

int main(){
    Foo foo;
}

相关问题