tags:

views:

184

answers:

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?

+4  A: 

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

Don Neufeld
+6  A: 

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.

Johannes Schaub - litb
This technique is called X-Macros - http://en.wikipedia.org/wiki/C_preprocessor#X-Macros
qrdl
Perfect, that's the hook I was looking for with my slow Monday befuddled brain... Put the definitions in a separate file and read it in twice.Thanks!
Mike Blackwell
One drawback of this technique is that the IDE and/or debugger may not be smart enough to parse these correctly. If this is the case - it is probably better to generate the list as part of the build process.
Hexagon
A: 

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
}
hlovdal