Reputation: 48038
Polymorphic structs are quite common in C but often involve explicit casts which allow for accidentally casting incompatible structs.
struct ID {
char name[32];
};
struct IntID {
struct ID id_base;
int value;
}
struct FloatID {
struct ID id_base;
float value;
}
void id_name_set(ID *id, const char *name)
{
strlcpy(id->name, name, sizeof(id->name));
}
/* macro that happens to use 'id_name_set', this is a bit contrived */
#define ID_NAME_SET_AND_VALUE(id, name, val) \
do { \
id_name_set((ID *)id, name); \
id->value = val; \
} while(0)
void func(void)
{
struct { int value; } not_an_id;
/* this can crash because NotID doesn't have an ID as its first member */
ID_NAME_SET_AND_VALUE(not_an_id, "name", 10);
}
The issue here is we can't type check the id
argument in the macro against a single type, since it could be an ID
or any struct with an ID
as its first member.
A lot of code I've seen simply casts to the struct all over the place, but it seems it is possible to have a more reliable method.
Is there a way to check at compile time?
Note, for the purpose of this question, we can assume all structs use the same member name for the struct they inherit from.
Note, I was hoping to be able to use something like this...
# define CHECK_TYPE_POLYMORPHIC(val, member, struct_name) \
(void)(_Generic((*(val)), \
/* base-struct */ struct_name: 0, \
/* sub-struct */ default: (_Generic(((val)->member), struct_name: 0))))
/* --- snip --- */
/* check that `var` is an `ID`, or `var->id_base` is */
CHECK_TYPE_POLYMORPHIC(var, id_base, ID);
...but this fails for ID
types in the default
case - because they have no id
member.
So far the only way I found to do this is to type-check against a complete list of all structs, which isn't ideal in some cases (may be many — or defined locally, therefore not known to the macro, see: Compile time check against multiple types in C?).
Upvotes: 3
Views: 172
Reputation: 1
The closest thing in C11 (the latest C standard) for compile-time polymorphism is its
type-generic expressions using the _Generic
keyword, but I am not sure it fits your needs.
The GCC compiler also gives you its __builtin_type_compatible_p
with which you could build e.g. some macros.
You could also customize GCC with some MELT extensions
Upvotes: 1
Reputation: 78903
You shouldn't use casts. A cast supposes that you know what you are doing and in the worst case leads to undefined behavior. You'd have to rely on the fact that the types that you are interested in all have that struct ID
field with the same name.
Then, in the case that you present where you actually have a do-while
kind of functional macro, you can easily place an auxiliary variable:
#define ID_NAME_SET_AND_VALUE(id, name, val) \
do { \
ID* _id = &((id)->id_base)); \
id_name_set(_id, (name)); \
_id->value = (val); \
} while(0)
If all goes well, this is a nop, if not it is a constraint violation and aborts compilation.
In a context where you can't place a variable you could use a compound literal, something like
(ID*){ &((id)->id_base)) }
Upvotes: 1