menu

Questions & Answers

Implicit casting to union type?

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?

Comments:
2023-01-11 09:12:44
Do you know that setting the age will destroy the name and vice-versa?
2023-01-11 09:12:44
Sounds like a job for _Generic
2023-01-11 09:12:44
@Barmar _Generic will make mess. At the moment I am not sure if OP knows what union is
2023-01-11 09:12:44
@0___________ They're assigning different variables, so nothing is being destroyed.
2023-01-11 09:12:44
I cant understand then why two different unions instead one struct. It looks like X-Y problem for me
2023-01-11 09:12:44
You can use compound literals: set_person_param(NAME, (person_param_val_t){ .name = "Alex"}); set_person_param(AGE, (person_param_val_t ){ .age = 5} );
2023-01-11 09:12:44
"I would need to write many (very similar) setter functions which would take a lot more lines of code", 4 lines for a setting function as opposed to 3 lines to add a case isn't that much more, and it's more clear.
2023-01-11 09:12:44
@dbush do you understand why union and strange global variables
2023-01-11 09:12:44
In regard to your example code, neither (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).
2023-01-11 09:12:44
sounds like a job for a simple macro , no unions or any such fancy stuff
2023-01-11 09:12:44
People do not have either a name or an age. They have both a name and an age. And it is perfectly possible to have an age and a name at the same time. So it would be more sensible to store both age and name in a struct, then find a way to mark either item invalid. For example both members could be pointers to data set to NULL when not initialized.
2023-01-11 09:12:44
@EricPostpischil Casting to unions is not a part of standard C, but GNU C extension allows it.
Answers(3) :

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.

Comments:
2023-01-11 09:12:44
Where are magic g_... variables as in the OP example