Define array and symbolic indices at same time

Define array and symbolic indices at same time

秉烛思 发布于 2021-11-27 字数 590 浏览 719 回复 3 原文

I'm trying to think of a clever way (in C) to create an array of strings, along with symbolic names (enum or #define) for the array indices, in one construct for easy maintenance. Something like:

const char *strings[] = {
M(STR_YES, "yes"),
M(STR_NO, "no"),
M(STR_MAYBE, "maybe")
};

where the result would be equivalent to:

const char *strings[] = {"yes", "no", "maybe"};
enum indices {STR_YES, STR_NO, STR_MAYBE};
(or #define STR_YES 0, etc)

but I'm drawing a blank for how to construct the M macro in this case.

Any clever ideas?

如果你对这篇文章有疑问,欢迎到本站 社区 发帖提问或使用手Q扫描下方二维码加群参与讨论,获取更多帮助。

扫码加入群聊

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

み格子的夏天 2022-06-07 3 楼

It is not required to put this into specific .def files; using only the preprocessor is perfectly possible. I usually define a list named ...LIST where each element is contained within ...LIST_ELEMENT. Depending on what I will use the list for I will either just separate with a comma for all but the last entry (simplest), or in the general case make it possible to select the separator individually on each usage. Example:

#include <string.h>

#define DIRECTION_LIST 
DIRECTION_LIST_ELEMENT( up,    DIRECTION_LIST_SEPARATOR ) 
DIRECTION_LIST_ELEMENT( down,  DIRECTION_LIST_SEPARATOR ) 
DIRECTION_LIST_ELEMENT( right, DIRECTION_LIST_SEPARATOR ) 
DIRECTION_LIST_ELEMENT( left,  NO_COMMA )

#define COMMA ,
#define NO_COMMA /**/

#define DIRECTION_LIST_ELEMENT(elem, sep) elem sep
#define DIRECTION_LIST_SEPARATOR COMMA
typedef enum {
    DIRECTION_LIST
} direction_t;
#undef DIRECTION_LIST_ELEMENT
#undef DIRECTION_LIST_SEPARATOR

#define DIRECTION_LIST_ELEMENT(elem, sep) void (*move_ ## elem)(struct object_s * object);
#define DIRECTION_LIST_SEPARATOR NO_COMMA
typedef struct object_s {
    char *name;
    // ...
    DIRECTION_LIST
} object_t;
#undef DIRECTION_LIST_ELEMENT
#undef DIRECTION_LIST_SEPARATOR

static void move(object_t *object_p, const char * direction_string)
{
    if (0) {
    }
#define DIRECTION_LIST_SEPARATOR NO_COMMA
#define DIRECTION_LIST_ELEMENT(elem, sep) 
    else if (strcmp(direction_string, #elem) == 0) { 
        object_p->move_ ## elem(object_p); 
    }
    DIRECTION_LIST
#undef DIRECTION_LIST_ELEMENT
#undef DIRECTION_LIST_SEPARATOR
}
恬淡成诗 2022-06-07 2 楼

This is a good place to use code generation. Use a language like perl, php or whatever to generate your .h file.

旧夏天 2022-06-07 1 楼

A technique used in the clang compiler source is to create .def files that contains a list like this, which is designed like a C file and can easily be maintained without touching other code files that use it. For example:

#ifndef KEYWORD
#define KEYWORD(X)
#endif
#ifndef LAST_KEYWORD
#define LAST_KEYWORD(X) KEYWORD(X)
#endif

KEYWORD(return)
KEYWORD(switch)
KEYWORD(while)
....
LAST_KEYWORD(if)

#undef KEYWORD
#undef LAST_KEYWORD

Now, what it does is including the file like this:

/* some code */
#define KEYWORD(X) #X, 
#define LAST_KEYWORD(X) #X

const char *strings[] = { 
#include "keywords.def"
};

#define KEYWORD(X) kw_##X, 
#define LAST_KEYWORD(X) kw_##X

enum { 
#include "keywords.def"
};

In your case, you could do similar. If you can live with STR_yes, STR_no, ... as enumerator names you could use the same approach like above. Otherwise, just pass the macro two things. One lowercase name and one uppercase name. Then you could stringize the one you want like above.