1

I have the following type definition:

typedef struct {
  int (*function)(int argc, char *argv[]);
  char *name;
} command_t;

The member function is a function pointer and the member name is a string which will store the name of the function.

To initialize a variable of type command_t, I wrote the following macro:

#define COMMAND(x) (command_t){.function = x, .name = #x}

Here's how I currently initialize an array of command_t:

int ls(int argc, char *argv[]);
int echo(int argc, char *argv[]);
int cat(int argc, char *argv[]);
int mkdir(int argc, char *argv[]);

command_t cmd_list[] = {COMMAND(ls), COMMAND(echo), COMMAND(cat), COMMAND(mkdir)};

I would like to be able to initialize an array of command_t as such:

command_t cmd_list[] = COMMAND(ls, echo, cat, mkdir);

or

command_t cmd_list[] = {COMMAND(ls, echo, cat, mkdir)};

I know that COMMAND has to be a variadic macro to do so but I don't know how to write it.

A.Lacasse
  • 125
  • 10
  • 1
    I don't think it can be done with variadic macros. The preprocessor does not have the capability to deconstruct the VA_ARGS and process each arg. It can only do a simple expand of the VA_ARGS in its body. IMHO X-macros would be more appropriate here. – kaylum Jun 21 '21 at 00:12
  • "I would like to be able to initialize an array of command_t as such" Why? It doesn't improve readability much, it just makes your code look more mysterious. If anything you should make a macro for the whole init list. – Lundin Jun 21 '21 at 08:16

2 Answers2

1
//stringification of the argument Arg
#define PP_STRINGIFY(Arg) PP_STRINGIFY_(Arg)
#define PP_STRINGIFY_(Arg) #Arg

//concatenation of the two arguments
#define PP_CAT2(_1, _2) PP_CAT_(_1, _2)
#define PP_CAT_(_1, _2) _1##_2

//enumerate the number of arguments (min:1, max: 8)
#define PP_VA_NUM_ARGS(...) PP_VA_NUM_ARGS_(__VA_ARGS__,8,7,6,5,4,3,2,1)
#define PP_VA_NUM_ARGS_(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N

//a single command initializer
#define COMMAND(x) (command_t){.function = x, .name = PP_STRINGIFY(x)}

//command list initializer
#define COMMAND_LST(...) { PP_CAT2(COMMAND_LST_,PP_VA_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__) }

//implement as many as you need
//maybe you need to extend PP_VA_NUM_ARGS (current max limit 8)
#define COMMAND_LST_1(_1) COMMAND(_1)
#define COMMAND_LST_2(_1,_2) COMMAND(_1), COMMAND(_2)
#define COMMAND_LST_3(_1,_2,_3) COMMAND_LST_2(_1,_2), COMMAND(_3)
#define COMMAND_LST_4(_1,_2,_3,_4) COMMAND_LST_3(_1,_2,_3), COMMAND(_4)

typedef struct {
    int (*function)(int argc, char *argv[]);
    char *name;
} command_t;

int ls(int argc, char *argv[]);
int echo(int argc, char *argv[]);
int cat(int argc, char *argv[]);
int mkdir(int argc, char *argv[]);

int main()
{
    command_t cmd_lst[] = COMMAND_LST(ls, echo, cat, mkdir);
    return 0;
}

CPP output via

gcc -E main.c

gives you

command_t cmd_lst[] = { 
    (command_t){.function = ls, .name = "ls"}, 
    (command_t){.function = echo, .name = "echo"}, 
    (command_t){.function = cat, .name = "cat"}, 
    (command_t){.function = mkdir, .name = "mkdir"} 
};
Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11
1

I think you are already close to the most readable form. Try to create the code as simple as possible. So instead of inventing some strange macro that no C programmer understands what it does without going though a jungle of macros, you could make a macro for the whole initializer list - that's fairly common practice.

That is: command_t cmd_lst[] = COMMAND_INIT; And then:

#define COMMAND(cmd) { .function = cmd, .name = #cmd }
#define COMMAND_INIT { COMMAND(ls), COMMAND(echo), COMMAND(cat), COMMAND(mkdir) }

Yet another option is X macros. They are not necessarily more readable, but could maybe lead to reduction of code repetition elsewhere:

#define FUNC_LIST(X) \
  X(ls)              \
  X(echo)            \
  X(cat)             \
  X(mkdir)           \

...

#define COMMAND_INIT(func) { .function = func, .name = #func },  
command_t cmd_lst[] = { FUNC_LIST(COMMAND_INIT) };
Lundin
  • 195,001
  • 40
  • 254
  • 396