Generic Containers in C: A Variadic Type

Martin Uecker, 2025-09-21


I discuss the implementation of type and bounds safe generic containers in C. Previously, I discussed a span type, bounds checking using arrays, a a vector type, a maybe type.

This time, I will discuss variadic type. This type can used to store a value of an arbitrary type, while maintaining type safety at run-time.


	   variadic *vp = variadic_make(int, 5);
    	   variadic_access(int, vp)++;
	

Again, we create this type using a generic macro that expands into a structure that contains a typeid as a string and the generic value.


	typedef struct variadic_id { const char *typeid; } variadic;
	#define variadic(T) struct val_##T { variadic super; T value; }
	

But this time, this is not the type we want to use in the code. Instead, we want to operate on the type variadic where the type of the value was erased. We achieve this simply by using a pointer to the first element of the structure, which contains the typeid and was wrapped into a structure type variadic for exactly this purpose.
For convenience, we directly construct pointers to this type using a macro variadic_make where internally we create a string as the typeid for the type argument T.


	#define _variadic_id(T) STRINGIFY(CONCAT(val_, T))
	#define variadic_make(T, x) &((variadic(T)){ { _variadic_id(T) }, (x) }.super)
	

We can now pass around pointers to this type to generic functions.


	void generic_algo(variadic *m);
	
	static int algo(int x)
	{	
		variadic *m = variadic_make(int, x);
		generic_algo(m);
		return variadic_access(int, m);
	}
	

Later, we access the stored value using variadic_access. How does this work? We check the typeid at runtime using a string comparison and then use the fameous containerof macro to convert the pointer to the first argument to a full structure of type variadic(T). We can then simply return the stored value.


	#define variadic_access(T, x) 					\
	(*({								\
		auto _x = (x);						\
		CHECK(0 == strcmp(_x->typeid, _variadic_id(T)));	\
		&containerof(_x, variadic(T), super)->value;		\
	}))
	

Instead of handling the error condition with the CHECK macro, I could create an lvalue that points nowhere in case of an error because it then corresponds to (*({ (void*)0; })), relying on the null sanitizer or the operating system to transform it into a run-time trap for safety. This is what I did for maybe, but one can also add an explicit check as shown here.

One could ask how the generic function can use the value, as it is now hidden inside the type-erased variadic type. The answer is that it calls other generic functions, which may be passed using a table of function pointers.


	typedef struct genops {
		void (*increment)(variadic *);
		bool (*test)(variadic *);
		void (*dbl)(variadic *, variadic *);
	} genops;

	static void generic_algo(genops vt, variadic *val)
	{
		vt.dbl(val, val);

		if (vt.test(val))
			vt.increment(val);
	}
	

You can find the full example here: Godbolt

If you compile this program using a high optimization level, the compiler will completely remove all overhead and checking and directly return the result.


"main":
        mov     eax, 35
        ret
	

If you want, you can check out my experimental library where I am experimenting with these ideas: link. If you have ideas on how to do this better, let me know!