Suppose we have the following well-known inheritance pattern in C with a parent, a derived child struct
and a vtable:
struct parent;
struct vtable {
void(*op)(struct parent *obj);
};
struct parent {
struct vtable *vtable;
};
struct child {
struct parent parent;
int bar;
};
The vtable for struct child
is then usually defined in the following way:
void child_op(struct parent *obj) {
// Downcast
struct child *child = (struct child *)obj;
/* Do stuff */
}
static struct vtable child_vtable = {
.op = child_op,
};
We can then use the vtable and have dynamic dispatch:
void foo(struct parent *obj) {
obj->vtable->op(obj);
}
There are cases however, where we know the concrete type of a struct child
, e.g. directly after the struct was allocated. In this case, it makes sense to skip the indirect function call through the vtable and call the specific implementation of op
for struct child
directly. To dos this however, we have to convert the pointer first. The “upcast” can be performed without actual casting.
void bar(void) {
struct child *obj = malloc(sizeof(*obj));
obj->parent.vtable = &child_vtable;
// Need convert pointer to child into pointer to parent first
child_op(&obj->parent);
}
Would it be legal however,
to declare the child_op
function taking a parameter of struct child *
instead of the base type struct parent *
,
because we know, that it will be called only with pointers to struct child
objects and cast the function pointer assigned to the vtable instead:
void child_op(struct child *obj) {
// No need to cast here anymore
/* Do stuff */
}
// Cast only when creating the vtable
// Is this undefined behaviour?
static struct vtable child_vtable = {
.op = (void (*)(struct parent *))child_op,
};
void foo(struct child *obj) {
// No need to do any conversion here anymore
child_op(obj);
}
As one can see, this would save us from having to cast in child_op
and converting struct child *
into struct parent *
in cases, where we are sure, we have a valid pointer to a struct child
(and no pointer to a e.g. struct grandchild
).
A similar issue is discussed here and I conclude from it, that it would be legal if the types void (*)(struct parent *)
and void (*)(struct child *)
are compatible.
It certainly works in practice with the compilers and platforms I tested. If it is indeed undefined behaviour, are there any cases, where this would actually cause problems and the missing cast would actually have done something? Are any well-known projects using this scheme?
EDIT: You can find the examples on godbolt: