预处理器中的命令串联

fra*_*i87 2 c c++ c-preprocessor

最初这必须是另一个问题的最后一段(这一个),但我注意到主题不是那么相似,因此我想发布另一个问题.

我想要完成的是制作一个自动化系统,以简单的方式编译一些动作.

例如,而不是写作

switch (var)
{
    case SM_1:
        printf("Case 1");
        break;
    case SM_2:
        printf("Case 2");
        break;
    case SM_3:
        printf("Case 3");
        break;
}
Run Code Online (Sandbox Code Playgroud)

我会更容易写出类似的东西

#define CASES 
#define CreateCaseX(s,l) CASES += \
    case s: \
        printf(l); \
        break;

CreateCaseX(SM_1, "Case 1")
CreateCaseX(SM_2, "Case 2")
CreateCaseX(SM_3, "Case 3")

...

switch(var)
{
    CASES
}
Run Code Online (Sandbox Code Playgroud)

我不希望该CASES +=语句实际工作,但是......是否有一些类似的构造我可以在预处理器中使用一些动作(函数调用,切换案例......)添加到列表然后让它们写入它们一次全部?

编辑:我认为我已经很好地解决了我的问题边界,但是看一些答案和评论我注意到这个问题可以用不同的方式解释.所以请原谅我,现在我会尝试更好地详细说明.

我正在写一个简单的状态机.每当我添加状态时,我都必须执行不同的代码修改,例如

  • 将状态添加到枚举中
  • 在switch case结构中添加状态名称,该结构返回当前状态的名称
  • 添加当该状态为当前状态时要执行的操作

例如,我可以在我的文件中包含以下行:

enum {
    SM_Case_1,
    SM_Case_2,
    SM_Case_3
} currentState;

void printState()
{
    switch (currentState)
    {
    case SM_Case_1:
        printf("State 1");
        break;
    case SM_Case_2:
        printf("State 2");
        break;
    case SM_Case_3:
        printf("State 3");
        break;
    }
}

void executeAction()
{
    switch (currentState)
    {
    case SM_Case_1:
        // Do nothing
        break;
    case SM_Case_2:
        globalVariable += 10;
        break;
    case SM_Case_3:
        printf("Error");
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

在我看来,有一个宏处理这个更容易(并且更容易维护),例如:

#define ENUMS
#define NAMES_CASES
#define ACTION_CASES
#define CreateState(s,l,a) \
    ENUMS += s, \
    NAMES_CASES += case s: printf(l); break; \
    ACTION_CASES += case s: a; break;

CreateState(SM_Case_1,"State 1",{})
CreateState(SM_Case_2,"State 2",globalVariable += 10)
CreateState(SM_Case_3,"State 3",printf("Error"))

enum {
    ENUMS
} currentState;

void printState()
{
    switch (currentState)
    {
    NAMES_CASES
    }
}

void executeAction()
{
    switch (currentState)
    {
    ACTION_CASES
    }
}
Run Code Online (Sandbox Code Playgroud)

我注意到编写一个可行的语法很棘手,所以可能这样的技术不存在,但是......如果有的话,知道它是有用的.

ric*_*ici 5

没有在宏中累积字符串的一般方法,但您可以通过将宏调用序列构建为宏来有效地定义列表.这必须在一个部分完成,但似乎这适用于你的例子.

一旦构建了列表,就可以多次使用它以用于不同的目的.该技术通常称为X宏.

这是一个简单的例子.(我个人更喜欢将宏名称传递给X宏的样式,而不是X像维基百科文章那样对宏名称进行硬编码.)

// Make the list (I added semicolons to the actions.)
#define STATES(X)                              \
  X(SM_Case_1,"State 1",{})                    \
  X(SM_Case_2,"State 2",globalVariable += 10;) \
  X(SM_Case_3,"State 3",printf("Error");)

//...

#define ENUM(S, L, A) S,    
enum {
    STATES(ENUM)
} currentState;
#undef ENUM

// ...

#define CASE(S, L, A) case S: printf("%s", L); break;    
void printState() {
    switch (currentState) {
    STATES(CASE);
    }
}
#undef CASE

// ...

#define CASE(S, L, A) case S: { A } break;
void executeAction() {
    switch (currentState) {
    STATES(CASE);
    }
}
#undef CASE
Run Code Online (Sandbox Code Playgroud)

请注意,后两个节足够相似,它们可以在宏中封装,如:

#define SWITCH_FUNC(NAME, CASE) \
  void NAME() { switch (currentState) { STATES(CASE); } }
Run Code Online (Sandbox Code Playgroud)

此外,您可以使用标记串联和字符串化将列表简化为两个参数而不是三个:

#define STATES(X)                   \
  X(State_1, {})                    \
  X(State_2, globalVariable += 10;) \
  X(State_3, printf("Error");)

#define CONCAT_(A, B)    A##B
#define STRINGIFY_(S)    #S
#define CASENAME(S)      CONCAT_(SM_, S)
#define ENUM(S,A)        CASENAME(S),
#define PRINT_CASE(S,A)  case CASENAME(S): printf("%s", STRINGIFY_(S)); break;
#define ACTION_CASE(S,A) case CASENAME(S): { A } break;
Run Code Online (Sandbox Code Playgroud)

(在coliru上运行,虽然预处理器输出有点难以阅读)

有时,您会希望使用预处理器条件来控制是否将单个项目放入列表中.请参阅本答案的第二部分,以获得实现这一目标的精彩技巧.