Let's say we have the following parameters describing a person: name (string) and age (unsigned int). I want to write a universal setter API function that someone can call to set either the name or the age of a specific person. The reason for that will be explained later below.
What I did is define an enum type of person parameter names:
typedef enum person_param_name
{
NAME,
AGE,
} person_param_name_t;
And also a union type for person parameter values:
typedef union person_param_val
{
char* name;
unsigned int age;
} person_param_val_t;
Now, the function can look like this:
int set_person_param(person_param_name_t param_name, person_param_val_t param_val)
{
int ret = 0;
switch (param_name)
{
case NAME:
g_person_name = param_val.name;
break;
case AGE:
g_person_age = param_val.age;
break;
default:
ret = -1;
break;
}
return ret;
}
The problem with this approach is that one can't simply call the setter function like this (compiler throws warning):
set_person_param(NAME, "Alex");
set_person_param(AGE, 5);
But they have to explicitly cast the param value to person_param_val_t
type, like this:
set_person_param(NAME, (person_param_val_t)"Alex");
set_person_param(AGE, (person_param_val_t )5);
The reason I want the universal setter function is because in the real program, I have a lot more parameters (close to 100) and I would need to write many (very similar) setter functions which would take a lot more lines of code.
Is there a better approach to this?
CodePudding user response:
If you change the union so that the field names are identical to the enum constants:
typedef union person_param_val
{
char* NAME;
unsigned int AGE;
} person_param_val_t;
Then you can create a macro which will pass a properly initialized compound literal:
#define set_person_param_ext(k,v) \
set_person_param(k, (person_param_val_t){.k=v})
So then this:
set_person_param_ext(NAME, "Alex");
set_person_param_ext(AGE, 5);
Will expand to this:
set_person_param(NAME, (person_param_val_t){.NAME="Alex"});
set_person_param(AGE, (person_param_val_t){.AGE=5});
CodePudding user response:
I could see:
typedef enum person_param_name {
NAME,
AGE,
} person_param_name_t;
typedef union person_param_val
{
char* name;
unsigned int age;
} person_param_val_t;
person_param_val_t person_param_val_init_charp(char *name) {
return (person_param_val_t){.name=name};
}
person_param_val_t person_param_val_init_u(unsigned age) {
return (person_param_val_t){.age=age};
}
#define MAKE_PERSON_PARAM_VAL(x) _Generic((x) \
, unsigned: person_param_val_init_u \
, char *:person_param_val_init_charp \
)(x)
int set_person_param(person_param_name_t param_name, person_param_val_t param_val);
#define set_person_param(a, b) \
set_person_param(a, MAKE_PERSON_PARAM_VAL(b))
int main() {
set_person_param(NAME, "Alex");
set_person_param(AGE, 5u);
}
With GCC with extension, you will get away with just:
#define set_person_param(a, b) \
set_person_param(a, (person_param_val_t)(b))
But I would not write such code. This is C. In C, you would write it all explicitly. I do not see a value in person_param_name
. You still have to enumerate all types explicitly inside set_person_param
. I would just write set_person_param_age(unsigned age)
and set_person_param_name(char *name)
explicitly. If not, I would consider rethinking the whole approach, as most probably you want to implement virtual function. I would advise, strongly consider not writing an interface with endless number of cases in enums, because you might end up with this. Instead, create objects with a pointer to the interface stored with a vtable.
CodePudding user response:
You do not need any magic. This macro is enough
#define set_person_param(param, val) g_person_##param.param = (val)
And this sample function:
int foo(void)
{
person_param_val_t g_person_name, g_person_age;
set_person_param(name, "Alex");
set_person_param(age, 5);
}
will be preprocessed to:
int foo(void)
{
person_param_val_t g_person_name, g_person_age;
g_person_name.name = "Alex";
g_person_age.age = 5;
}
As I understand it was something you wanted to archive. Your enum type is not needed.
If you want val
to be the same union type then:
#define set_person_param(param, val) g_person_##param = (val)
example:
set_person_param(name, (person_param_val_t){.name="Alex"});