My goal is to automatically generate the control logic of an old-school knowledge-based system, which is essentially a series of hundreds of similarly structured if-then statements. We also want to know properties of these statements in a sort of reflection, which the array literals assist in generating.
I've reviewed many of the answers here on SO, but most of them are in regards to arrays of string literals, which is not what I'm trying to accomplish.
I'm trying to make an X-macro which contains as one of its parameters an array of struct literals that can be used to initialize a stack-allocated array variable.
Here's some code that doesn't compile that shows my attempt:
#include <stdio.h>
typedef enum qty_t {
qty_None, qty_Some, qty_Many
} qty_t;
typedef enum arg_t {
arg_X, arg_Y, arg_Z
} arg_t;
typedef struct action_t {
int id;
qty_t quantity;
arg_t argument;
} action_t;
/* a change is a sequence of actions, and a change identifier */
/* the outermost {} are the array, the inner ones being the structs */
#define CHANGES \
CHANGE(1, ({{.id=1,.quantity=qty_None,.argument=arg_X}, \
{.id=2,.quantity=qty_Many,.argument=arg_Z}\
}\
)\
)\
CHANGE(2, ({{1,qty_Some,arg_Y}}))
int main(void) {
// your code goes here
#define CHANGE(change_id, actions) \
action_t current_actions ## change_id[] = actions;
CHANGES
#undef CHANGE
return 0;
}
How do I put an array literal of struct literals inside the CHANGE macro, specifically one that can be used to initialize the current_actions1 variable?
There is no such thing as "an array of struct literals", whether a literal itself or not. The elements of an array can be structs, and you can use struct literals to initialize elements of an array of structs (yuck), including in an array literal, but the elements of the array are not themselves literals. This may sound -- indeed, may be -- pedantic, but using consistent and correct terminology improves clarity of thought and of communication.
In fact, you are not using struct literals or array literals at all. You are simply using struct initializers inside an initializer for an array of structs. This simplifies the issue a bit already.
The first problem with your code is the parentheses you placed in the definition of macro CHANGE
, around the array initializers. These are part of the second argument to each invocation of the CHANGE
macro, and as such, they are emitted as part of the replacement text of macro CHANGES
. But they are not part of the the allowed syntax for initializers, so the resulting code is invalid.
I presume that the parentheses are there in the first place to avoid the commas inside the initializers being interpreted as separating macro arguments. An alternative way to solve that problem, atleast in this case, would be to make your definition of the CHANGE
macro variadic:
#define CHANGE(change_id, ...) \
action_t current_actions ## change_id[] = __VA_ARGS__;
This solves the problem for me, using the standard form of variadic macros as introduced in C99.
Another, more structured alternative would be to add macros for the struct initializers, so that the commas within and between do not affect macro argument identification. For example,
#define FIRST_ACTION(i,q,a) {.id=i,.quantity=q,.argument=a}
#define ACTION(i,q,a) ,FIRST_ACTION(i,q,a)
#define CHANGES \
CHANGE(1, {\
FIRST_ACTION(1,qty_None,arg_X)\
ACTION(2,qty_Many,arg_Z)\
})\
CHANGE(2, {\
FIRST_ACTION(1,qty_Some,arg_Y)\
})
// ...
#define CHANGE(change_id, actions) \
action_t current_actions ## change_id[] = actions;
CHANGES
Of course, all this supposes that there is some good value to be obtained from the X macro approach in your case. That requires at minimum that there be another use of the CHANGES
macro elsewhere in your code. None such is evident in what you present, but that could have been omitted. It's not immediately clear to me, however, that your particular CHANGES
macro affords additional uses that would not be better served by a different approach.