访问结构的联合成员的优雅方式

时间:2018-02-23 09:33:41

标签: c++ c c-preprocessor unions

有没有办法反转#define指令?

在以下示例中

#define ZERO 0
#define ONE 1
#define TWO 2
#define THREE 3

是否可以从整数值2中检索两个?

此示例来自C代码,但如果需要,我可以使用一些C ++代码。我的目标是能够分解这种形式的一些虚假的switch-case循环:

switch(num)
{
   case ZERO:
      return std::to_string(foo.V_ZERO);
   case ONE:
      return std::to_string(foo.V_ONE);
   case TWO:
      return std::to_string(foo.V_TWO);
   case THREE:
      return std::to_string(foo.V_THREE);
}

其中foo是这样的结构的实例:

struct Foo
{
   union Val
   { 
      int V_ZERO;
      int V_ONE;
      double V_TWO; // nonsense: just to say that types are not the same
      int V_THREE;
   };
};

我的约束如下:

  1. 我无法删除#define提供的功能,即我可以写一些等效的内容,例如枚举,但我不能丢失ZERO和0,ONE和1等之间的地图;
  2. 现有代码是用C语言编写的,我不能用C ++重写它。但是,我可以编写一些补充的C ++代码。
  3. 我有一些简化代码的想法,但我想知道是否有一种非常着名的方法,特别是通过一些模板或预处理器指令。

    编辑:添加std :: to_string的用法,说我不知道​​如何转换或处理联合中的多个类型。

5 个答案:

答案 0 :(得分:3)

如果要自动执行某些过程,可以选择使用xmacros。他们是hacky,但根据你的代码大小可能会使你的同事们的愤怒变得更容易维护(如增加新条目):

定义xmacro列表:

#define XLIST(xmacro) \
   xmacro(ZERO, 0) \
   xmacro(ONE, 1) \
   xmacro(TWO, 2) \
   xmacro(THREE, 3) \

然后在想要遍历所有项目时使用它:

// create an enum containing all items
#define xmacro(name, value) name,
enum Item
{
    XLIST(xmacro)
};
#undef xmacro

// get the number of items
#define xmacro(name, value) +1
const int NumberOfItems = 0 XLIST(xmacro);
#undef xmacro

// enum -> value
int itemToValue(enum Item item)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return value;
        XLIST(xmacro)
#undef xmacro
    }

    return -1;
}

// get enum name
const char * getItemName(enum Item item)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return #name;
        XLIST(xmacro)
#undef xmacro
    }

    return NULL;
}

这将被预处理为类似:

enum Item
{
    ZERO,
    ONE, 
    TWO, 
    THREE,
};

const int NumberOfItems = 0 +1 +1 +1 +1; // == 4

int itemToValue(enum Item item)
{
    switch (item)
    {
        case ZERO: return 0; 
        case ONE: return 1; 
        case TWO: return 2; 
        case THREE: return 3;   
    }

    return -1;
}

const char * getItemName(enum Item item)
{
    switch (item)
    {
        case ZERO: return "ZERO"; 
        case ONE: return "ONE"; 
        case TWO: return "TWO"; 
        case THREE: return "THREE";

    }

    return NULL;
}

您可以创建几乎任何您想要的映射,即对于您的结构,您将使用类似于@ Jean-François所写的内容:

// get struct value by item type
double getValueByName(enum Item item, struct Foo values)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return values.V_##name;
        XLIST(xmacro)
#undef xmacro
    }

    return -1;
}

答案 1 :(得分:1)

不,那是不可能的。

预处理器#define在预处理阶段由文本替换为其定义。实际的编译器永远不会看到符号。在运行时,您认为TWO 是什么意思?它已经被整个文字2所取代,所以没有变化。

const int two_define = TWO;
const int two_literal = 2;

都会将整数值2放在相应的变量中,没有任何魔法气味以某种方式将TWO2区分开来。编译器将看到const int two_define = 2;,因为预处理器符号将消失。

另外:假设您同时返回intdouble s,函数的实际返回类型是什么?这意味着它是double

答案 2 :(得分:1)

在一般情况下,您必须采用类似“X宏”的内容,如另一个答案中所建议的那样。但是,当其他一切都失败时,这是最后的手段。如果数字完全是任意的,你就必须去那里。

然而,在这种特定情况下,数字是相邻的并且从零开始。这需要enum与查找表相结合。实现它的标准方法是这样的:

#include <stdio.h>

typedef enum
{
  ZERO,
  ONE,
  TWO,
  THREE,
  SUPPORTED_NUMBERS
} number_t;

const char* STR_NUMBER [] =
{
  "ZERO",
  "ONE",
  "TWO",
  "THREE",
};

_Static_assert((sizeof STR_NUMBER / sizeof *STR_NUMBER) == SUPPORTED_NUMBERS,
               "Error: enum does not correspond to look-up table.");

int main (void)
{
  for(number_t i=0; i<SUPPORTED_NUMBERS; i++)
  {
    printf("%d %s\n", i, STR_NUMBER[i]);
  }
}

答案 3 :(得分:0)

对于这两个问题:

  

有没有办法反转#define指令?

     

是否可以从整数值2中检索两个?

也许数组映射结构适合您:

#define ZERO 0
#define ONE 1
#define TWO 2
#define THREE 3

typedef struct
{
    int number;
    const char *name;
} name_map_t;


#define MAP_NAME_STR(id) { id, #id },


static const name_map_t name_map_table[] =
{
    MAP_NAME_STR( ZERO  )
    MAP_NAME_STR( ONE   )
    MAP_NAME_STR( TWO   )
    MAP_NAME_STR( THREE )
};

然后只循环name_map_table数组以找到想要的对应物。

答案 4 :(得分:0)

我确定了你的问题,当我面对这种 Enumeration 模式时,我会告诉你我做了什么:

假设我有一个值的枚举,我要打印到switch,以表示数组索引等。

首先,我编写了一个包含文件,该文件将支持我将对此枚举类型执行的各种用法:

enum(INITIAL_STATE, 0, "This is the initial state")
enum(FLAG_READ, 1, "We have read the flag symbol")
...

然后我使用不同的宏定义来扩展寄存器的数据集,如下所示:

struct enum MyEnum {
#define enum(val,ix, string) val,
#include "myenumdef.i"
#undef enum
};

/* then, later in the same file ... */

char *MyEnumStrings[] = {
#define enum(val,ix, string) string,
#include "myenumdef.i"
#undef enum
};

/* .... */

char *MyEnumNames[] = {
#define enum(val, ix, string) #val, /* the string equivalent of enum names */
#include "myenumdef.i"
#undef enum
};

/* and more complex forms... like */

struct myEnumDesc {
    int   e_val;
    int   e_ix;
    char *e_name;
    char *e_desc;
} enum_table[] = {
#define enum(val,ix,string) val, ix, #val, string,
#include "myenumdef.i"
#undef enum
}; /* enum_table */


/* ... even, when I want to switch on them */
    switch(val) {
#define enum(val,ix,string) case VAL_##val: return string"("#val"="#ix")";
#include "myenumdef.i"
#undef enum
    default: return "invalid val";
    } /* switch */

只需准备一个上面的例子,然后通过C预处理器运行它,看看最终的C代码是怎样的。

你可以多次我需要这样做(即使是在一个文件中),我只需要改变宏enum的方式(在这种情况下,你甚至可以有几个宏名来定义和使用)在包含数据文件之前,它们构造更复杂的数据依赖性。如果你想在枚举中添加一个常量,你只需将它添加到.i文件中,按上面所示进行编码,一切都将适应变化。

你问过C-only解决方案,所以我给出了关于如何处理同一组项目的不同定义的更多近似方式。