Compile-time conditionals and unique constants using C11 _Generic

(Nhat Minh Lê (rz0) @ 2012-05-16 18:42:17)

About a month ago, I started playing around with the new C standard, namely C11, and its new features. While I have always been fond of some of them, such as explicit alignment support, others never really caught my interest. One of those not-so-interesting additions was the new _Generic construct, which allows rather limited ad-hoc compile-time type-based dispatching. While I reckon it was necessary in order to support the <tgmath.h> header in a meaningful way, as well as, perhaps, the growing number of integral types, I’d never seen it as anything more than that: an implementor’s device given standard blessing.

Well, that was until a few days ago. While experimenting with a macro of mine, I tried to improve it by using C11 features, and, in doing so, stumbled upon a rather less boring use of _Generic: compile-time "type-level" constants, and conditional compilation based on it! (Well, maybe it sounds obvious to some of you; for the rest, please read on!)

The basics of _Generic constants

Usually, symbolic constants are some arbitrary literal cast to an appropriate type, like this:

#define MY_MAGIC_PONY ((pony_t)-1)

This works for integer and floating point values, but is not guaranteed by the standard to work for pointers (except for 0, which yields a null pointer constant). You might want to try anyway, or you may take the address of some private dummy object instead.

#define MY_MAGIC_PONY (&my_magic_pony_)
extern struct pony my_magic_pony_;

Well, the bad news is that if you need that constant at run time, there’s not much I can do for you; you’ll have to keep using that. On the bright side, if the constant is only needed at compile time (e.g. if it is always generated by some macro, to induce a specific path in a conditional), there may be hope still.

With _Generic, you can now specify conditions based on types, which means, you can use dummy types instead of dummy values for your symbolic compile-time-only constants:

#define MY_MAGIC_PONY ((struct dummy_magic_pony *)NULL)
struct dummy_magic_pony;

#define MY_MAGIC_MACRO(..., p, ...)                   \
        ... _Generic((p),                             \
            struct dummy_magic_pony *: ...,           \
            default: ...) ...

OK, so what does this bring us? Well, three things:

Sample application 1: default arguments

So, we’ve seen how it’s done; sounds trivial, looks trivial, but what can we use it for? If this were just for me, I’d say "it comes in handy when you write complex macros sometimes", but I tried to come up with a simple enough example for this short article: default arguments. In case you may be wondering, though, this is for demonstration purposes only; don’t try this at home… well, what I mean is that probably nobody is going to use this, which is totally fine.

Anyway, while it is well-known(?) that default arguments were possible in C99 using preprocessor hacks, all solutions I’ve seen were rather cumbersome to implement (you’re welcome to prove me wrong; I haven’t tried too hard to look for the nicest way).

Probably the most usable solution comes with free keyword arguments (called named parameters, in some languages, as well) as a bonus. Just use a structure as your last parameter and stuff it with a compound literal. The disadvantage is that your defaults are all zeros (or whatever default initializer you get for the field type)… unless you name all of your arguments and include defaults in the compound literal; but that’s not what we’re aiming for here: we’re looking for plain old optional positional parameters.

You could also count the number of arguments in the variable argument list of a macro, but this requires a lot of boilerplate to define a version for each possible number of arguments.

Lastly, you could use arbitrary symbols and test for equality using preprocessor logic (for those who don’t know, it is possible with the standard C99 preprocessor to macro-branch on whether a given argument is equal to a few different things… it just requires quite a lot of work in the background and has some limitations as well — read the sources of any heavyweight preprocessor library and you’ll see; e.g. the one provided with COS). However, this has some quirks (which are outside of the scope of this article) and is definitely not for the casual preprocessor user. But the overall design is rather appealing, so we’ll keep with that except…

We can now use _Generic type constants instead of symbols. Let’s look at some code:

/* Generic definitions, for all functions thereafter. */
struct dflarg_;
#define DFLARG ((struct dflarg_ *)NULL)
#define IFDFL(arg, dflval)                                    \
        _Generic((arg),                                       \
            struct dflarg_ *: dflval,                         \
            default: (arg))

/*
 * Some function foobar with two optional arguments.
 * We take the second (non-optional) argument as part of
 * __VA_ARGS__ so the list is never empty.
 */
int foobar(int, int, int, int);
#define foobar(x, ...)                                        \
        foobar_((x), __VA_ARGS__, DFLARG, DFLARG)
#define foobar_(x, y, z, t, ...)                              \
        (foobar)((x), (y), IFDFL((z), 42), IFDFL((t), 36))

And here you go! Default arguments in C11 using _Generic.

Sample application 2: argument list length check

In our previous example, you’ve probably noticed that foobar now accepts extra arguments, which is not exactly ideal. More generally, it happens quite often with variadic macros (in my experience, especially "helper" macros that tend to be buried under more user-friendly ones, and that may need to handle the results of concatenating, splitting, mapping, and other operations on previous argument lists) that there’s a limit to how many arguments you actually want to handle.

Again, it is possible with the C99 preprocessor to check for the argument list length, but this typically involves even more heavy preprocessor logic than before, as you’ll want to do arithmetic with the preprocessor. There are ways to avoid the arithmetics, but as far as I know, they all require quite a bit of preprocessor metaprogramming in their own right.

With C11 and _Generic, however, you could just use a type-level constant… how? This is really just more of the above (well, it is the same trick, after all):

/* Our end-of-list marker is really just another default. */
#define EOARGS DFLARG
#define ISEOARGS(arg)                                         \
        _Generic((arg),                                       \
            struct dflarg_ *: true,                           \
            default: false)

#define foobar(x, ...)                                        \
        foobar_((x), __VA_ARGS__, DFLARG, DFLARG, EOARGS)
#define foobar_(x, y, z, t, eoargs, ...)                      \
        (_Static_assert(ISEOARGS(eoargs),                     \
            "too many arguments to foobar"),                  \
        (foobar)((x), (y), IFDFL((z), 42), IFDFL((t), 36)))

If you try this code… it should fail. That is because _Static_assert is a declaration according to the C11 grammar. This can be worked around by wrapping the _Static_assert in an inline structure declaration within a compound literal, like so:

#define inline_static_assert(x, s)                            \
        ((void)(const struct { int _unused;                   \
            _Static_assert((x), s); }){ 0 })

Unfortunately, this (supposedly corrected) code does not compile with Clang 3.0. Otherwise, we’d get the following final macro for foobar_:

#define foobar_(x, y, z, t, eoargs, ...)                      \
        (inline_static_assert(ISEOARGS(eoargs),               \
            "too many arguments to foobar"),                  \
        (foobar)((x), (y), IFDFL((z), 42), IFDFL((t), 36)))

I only have the N1570 draft so I can’t say for sure that this is correct, but my copy specifically states that a _Static_assert can occur inside a structure declaration (§ 6.7.2.1).

Hence we’re left with emulating _Static_assert, with the traditional negative array size trick, for example:

#define inline_static_assert(x, s)                            \
        ((void)(char * [(x) ? 1 : -1]){ s })

Now, the following call will fail to compile, although with an ugly message — can’t have it all, I guess:

foobar(6, 7, 1+1, 3*4, 5/5);

Conclusion

Well, this is it. _Generic is not widely supported yet, but hopefully support will come soon enough (and in the meantime, you can use P99 if you’re so inclined), at least for people living outside of the C-hater kingdom that Visual Studio has become. And once it does, I think it will help with simplifying some preprocessor hacks that used to require a lot of added ma^Wlogic in order to branch at macro-expansion time.