Statically asserting types in C11

(Nhat Minh Lê (rz0) @ 2013-03-22 10:08:41)

Yesterday, at the lab. Just another day at work… but then! After wrapping up the last lemma of my proof, I spontaneously decided my labmate needed to know more about C and its typing system, and even though we discussed none of the stuff I’m going to talk about now, it brings me to today’s topic: how to force the compiler to do some type checking at compile time (in C11)?

That might be a bit of a dumb question, so let me clarify. The compiler does a lot of type checking by itself, but imagine that for some reason, you have two type names and you want to know whether they are the same. Of course, you could just look at them, but what if you want to do it programmatically? What about if you had a value and a type name?

This blog post is all about trying to answer that, so, if you don’t really care about fun (modern) C tricks that you’ll probably never use, you can stop reading now. :-) If not…

Checking whether two types are equal

The setting is as follows: you have two type names T and U, that you probably got through layers and layers of hardcore preprocessor macroing. You’ve been dispatching, splitting, combining, and you’re now wondering whether that type T the user gave you matches the type U of the specialized routine you’ve selected — just as a precaution. (We’ll assume the types are "simple types" that only have a left-hand-side component.)

Solution 1: with variable definitions

There’s a couple of things you can try, depending on the current syntactic context. If you have access to variable definitions (at file or function scope) and don’t mind relying on dead code elimination from your compiler, you can simply create two variables and try to assign one to the other, like so:

#define CHECK_TYPE_COMPAT(T, U) T t; U u = t

If U can’t be assigned to T, the program won’t type check and the compiler will complain. Assignment is great since almost every type has it (exceptions being arrays and incomplete types). There’s one problem, however: CHECK_TYPE_COMPAT(int *, const int *) compiles happily. You might be tempted to write the following, then:

#define CHECK_TYPE_COMPAT(T, U) T t0; U u0 = t0; U u1; T t1 = u1

It does solve the above issue, but what about CHECK_TYPE_COMPAT(void *, int *) or CHECK_TYPE_COMPAT(double, int)? Obviously, this method does not work well if you want to test compatibility (equality, in standard jargon), but sometimes you just want to know whether U is assignable to T, and for that purpose, it’s a fine technique.

Another issue here is that you’re creating variables. In function scope, you can put a block around your definitions, to isolate them; however, at the top level, you’ll have to mint variable names, e.g., with __LINE__.

Solution 2: with typedef

With C11, you now have a pretty nifty alternative in the form of typedef. Of course, typedef itself is hardly a new construct. What’s new, however, is that you can now have multiple definitions of a typedef name. The only constraint is that they must be equal… which is exactly what we’re looking for!

#define CHECK_TYPE_COMPAT(T, U)                     \
    typedef T TC##__LINE__; typedef U TC##__LINE__

And here you go, manual type checking for everybody! And we don’t even generate variables anymore. Isn’t that perfect?

Well, of course, the answer is no; using __LINE__ means you can only have one of these per line, which may become problematic if you’re macro-expanding a lot, but there isn’t much you can do about it as long as you rely on introducing declarations. Besides, it doesn’t work everywhere…

Solution 3: with assignments

If you’ve been following, you should notice that the previous solutions only work if you have access to declarations, which excludes cases when you’d like to do it from within expressions. For these special occasions, you can use assignments instead… which brings us to a related but slightly different problem.

Checking whether a value can be assigned to a type

Here’s another story: you are given a type name T and an expression x. You want to know whether you could assign x to an object of type T. That’s a pretty legitimate situation when writing type-aware and other kinds of pseudo-polymorphic macros, I guess.

In any case, this check most probably needs to take place in the context of an expression, otherwise, x would be pretty limited. So, let’s assume we are in an expression. We can’t declare any new variable, so the previous tricks won’t work. But C99 offers a nice little feature I’m sure you all know and love (I do): compound litterals.

Compound litterals are nice in that they let you create anonymous aggregate values… but they don’t actually need to be constant. In the general case, compount litterals introduce anonymous objects. Using that fact, we can come up with a first attempt:

#define CHECK_ASSIGNABLE(T, x) ((T){ 0 } = (x))

To check whether you can assign x to an object of type T… simply create an object of type T and try to assign to it. You need to pass something inside the braces, so I put 0. It’s fine, though, since zero is the default initializer for every type in C.

Now, the only issue is that we just want to check, not read the value of x (which might be a side-effecting expression). Time to apply the good old tricks to deny evaluation! Finally, we get something like this:

#define CHECK_ASSIGNABLE(T, x) (0 && ((T){ 0 } = (x)))

This concludes this short blog post. You can try it at home, too, if your compiler supports C99 (or C11 for the typedef method above). Also, I haven’t given any code for solution 3; it’s very similar to CHECK_ASSIGNABLE. I’ll leave it as an exercise to the bored reader. If you’re really bored, you could also try to think about what the limits of these techniques are — of course, they have shortcomings, since they’re basically hacks, but in a sense, I find it amazing enough that such tricks exist (and work) in the first place.