date: 2017-06-05 layout: post title: Limited “generics” in C without macros or UB
tags: [C]
I should start this post off by clarifying that what I have to show you today is
not, in fact, generics. However, it’s useful in some situations to solve the
same problems that generics might. This is a pattern I’ve started using to
reduce the number of void*
pointers floating around in my code: multiple
definitions of a struct.
Errata: we rolled this approach back in wlroots because it causes problems with LTO. I no longer recommend it.
Let’s take a look at a specific example. In
wlroots, wlr_output
is a generic type
that can be implemented by any number of backends, like DRM (direct rendering
manager), wayland windows, X11 windows, RDP outputs, etc. The wlr/types.h
header includes this structure:
```c struct wlr_output_impl; struct wlr_output_state;
struct wlr_output { const struct wlr_output_impl impl; struct wlr_output_state state; // […] };
void wlr_output_enable(struct wlr_output output, bool enable); bool wlr_output_set_mode(struct wlr_output output, struct wlr_output_mode mode); void wlr_output_destroy(struct wlr_output output); ```
wlr_output_impl
is defined elsewhere:
```c struct wlr_output_impl { void (enable)(struct wlr_output_state state, bool enable); bool (set_mode)(struct wlr_output_state state, struct wlr_output_mode mode); void (destroy)(struct wlr_output_state *state); };
struct wlr_output wlr_output_create(struct wlr_output_impl impl, struct wlr_output_state state); void wlr_output_free(struct wlr_output output); ```
Nowhere, however, is wlr_output_state
defined. It’s left an incomplete type
throughout all of the common wlr_output
code. The “generic” part is that each
output implementation, in its own private headers, defines the
wlr_output_state
struct for itself, like the DRM backend:
c
struct wlr_output_state {
uint32_t connector;
char name[16];
uint32_t crtc;
drmModeCrtc *old_crtc;
struct wlr_drm_renderer *renderer;
struct gbm_surface *gbm;
EGLSurface *egl;
bool pageflip_pending;
enum wlr_drm_output_state state;
// [...]
};
This allows implementations of the enable
, set_mode
, and destroy
functions
to avoid casting a void*
to the appropriate type:
```c static struct wlr_output_impl output_impl = { .enable = wlr_drm_output_enable, // […] };
static void wlr_drm_output_enable(struct wlr_output_state output, bool enable) { struct wlr_backend_state state = wl_container_of(output->renderer, state, renderer); if (output->state != DRM_OUTPUT_CONNECTED) { return; } if (enable) { drmModeConnectorSetProperty(state->fd, output->connector, output->props.dpms, DRM_MODE_DPMS_ON); // […] } else { drmModeConnectorSetProperty(state->fd, output->connector, output->props.dpms, DRM_MODE_DPMS_STANDBY); } }
// […] struct wlr_output output = wlr_output_create(&output_impl, output); ```
The limitations of this approach are apparent: you cannot work with multiple
definitions of wlr_output_state
in the same file. However, you get improved
type safety, have to write less code, and improve readability.