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?
age
will destroy the name
and vice-versa? _Generic
set_person_param(NAME, (person_param_val_t){ .name = "Alex"}); set_person_param(AGE, (person_param_val_t ){ .age = 5} );
(person_param_val_t)"Alex"
nor (person_param_val_t )5
will work. The C standard defines only casts to void
or scalar types, not to unions (or structures or arrays). 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});
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"});
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.
g_...
variables as in the OP example