chood.net/blog/

Convenient C macros: enum(X), struct(X), union(X)

Sunday, June 30, 2024

Of the many C macros I set up when starting a new project, three of the most clever (i.e. hacky) are these:

#define enum(X)   typedef enum X X; enum X
#define struct(X) typedef struct X X; struct X
#define union(X)  typedef union X X; union X

(Yes, I have a low tolerance for hackiness when it comes to C macros.)

If their function is not immediately obvious, let me show you how they are used:

struct (Rect) {
	int32_t x;
	int32_t y;
	int32_t width;
	int32_t height;
};

enum (Http_Request) {
	HTTP_REQUEST_GET = 1,
	HTTP_REQUEST_POST,
	HTTP_REQUEST_HEAD,
	HTTP_REQUEST_PUT,
};

union (Value) {
	int   integer;
	char *string;
};

Note that, unlike ordinary type declarations, in the example above the type name is enclosed in parentheses. This causes the preprocessor to read them as function macro invocations, translating them into this (output from gcc -E):

typedef struct Rect Rect; struct Rect {
 int32_t x;
 int32_t y;
 int32_t width;
 int32_t height;
};

typedef enum Http_Request Http_Request; enum Http_Request {
 HTTP_REQUEST_GET = 1,
 HTTP_REQUEST_POST,
 HTTP_REQUEST_HEAD,
 HTTP_REQUEST_PUT,
};

typedef union Value Value; union Value {
 int integer;
 char *string;
};

These macros allow me to declare a type in the “tagged” namespace (where you have to include the struct, union, or enum keyword before the type name), and then automatically add them to the untagged type namespace. This is like how type names work in C++.

These macros are not portable because enums can’t be forward-declared without GNU extensions (and I’m not sure about giving a macro the same name as a keyword). However, they work in Clang and GCC, and I find them quite convenient.

One caveat is that these macros turn what looks like one declaration into two. I don’t think there is a realistic scenario where this could cause an issue, and if that did happen, it would be caught by the compiler because the types need to be known statically.

Using these macros doesn’t break code that uses the ordinary type declarations because the preprocessor won’t read declarations like this as an invocation of the struct macro:

struct foo {
	int index;
};