Detecting if an expression is constant in C

21 Apr 2025

Here's a fun little language puzzle: implement a macro that takes an (integer or floating) expression as an argument and:

There's a number of ways to solve this, depending on which C standard you're using and whether compiler extensions are allowed or not. Here are a few I've come across along with some pros and cons.

constexpr compound literals

If you're using C23 or later then you can specify a storage duration for compound literals. Combined with typeof and constexpr, both of which were also standardized in C23, you can use the following:

#define C(x) ( (constexpr typeof(x)){x} )

This keeps the type intact, and since initializers of constexpr storage duration need to be constant expression, the compiler will ensure that x is a constant expression.

Cons:


Earlier version of this article used static instead of constexpr. Since static storage duration requires initializer to be constant expression, the compiler would ensure it.

However, constexpr has slightly better semantics here because unlike static, constexpr mandates that the resulting expression (not merely the initializer) will also be a constant expression.


__builtin_constant_p

If using GNU extensions is not a problem, then you can use __builtin_constant_p, which returns true when an expression is constant along with __builtin_choose_expr for selection.

__attribute((error("not constant"))) int notconst(void);

#define C(x) __builtin_choose_expr(__builtin_constant_p(x), (x), notconst())

When __builtin_constant_p returns true, __builtin_choose_expr picks the first expression. Otherwise, it calls a dummy non-existent function declared with the error attribute, causing a compilation error.

Unlike ternary operator, __builtin_choose_expr isn't subject to the type promotion rules and thus keeps the type of the expression intact.

Cons:

static_assert

This trick was shown to me by constxd in an IRC channel and uses C11+ static_assert to ensure the expression is static. But how do we "return" the expression back? Well... with a bit of sizeof + anon struct magic:

#define C(x) ((x) + 0*sizeof( \
    struct { _Static_assert((int)(x) || 1, ""); char tmp; } \
))

This looks very wonky. How is a static_assert allowed inside a struct declaration? It's because the standard classifies static_assert as a declaration which declares nothing (don't ask me why). So syntactically, you can just put it inside a struct.

The addition by 0*sizeof(...) is effectively a no-op, leaving the value unchanged, but the type may change due to promotion.

Cons:

sizeof + compound literal with array type

This uses a similar trick as the above but instead of static_assert, it uses a compound literal array type to ensure that the expression is constant:

#define C(x) ( (x) + 0*sizeof( (char [(int)(x) || 1]){0} ) )

A couple things to note:

  1. Since C99, arrays (and array types) can be of variable length. But compound literals do not accept variable-length types, which does the verification for us.
  2. Standard C (unfortunately) forbids zero-length arrays, hence the || 1.
  3. Array sizes beyond a certain limit cannot be declared (even if no storage is being allocated). On my 64 bit PC, gcc refuses sizes above PTRDIFF_MAX (263-1) bytes, and clang is even more conservative and rejects sizes above 61 bits. Aside from supporting 0, the || 1 also clamps down the array size to 1.

The only advantage of this approach over static_assert is that it doesn't require C11 and can be used in C99. Other than that it inherits all the problems that comes with static_assert:

sizeof + enum constant

Because enum constants are required to be integer constant expression, we can use them instead of compound literal:

#define C(x) ( (x) + 0*sizeof( enum { tmp = (int)(x) } ) )

However, there's one glaring issue with this: unlike the compound literal, the enum constant "leaks out". Meaning that you cannot use this macro more than once. You could try to use pre-processor concat to append the line number (__LINE__) but then you can't use this macro more than once on the same line.

Here's a neat (or cursed) little solution I came up with: declare the enum inside a function parameter to give it "scope":

#define C(x) ( (x) + 0*sizeof(void (*)(enum { tmp = (int)(x) })) )

This works. But both gcc and clang warn about the enum being anonymous... even though that's exactly what I wanted to do. And this cannot be silenced with #pragma since it's a macro, so the warning occurs at the location where the macro is invoked.

Practically there's not much reason to use this, but it's C89 compatible, if that's something you care about. Cons:

Comma operator

All the macros that are using (x) + 0*sizeof(...) trick suffer from the type potentially changing. There's a simple and elegant solution to this, put the sizeof in a separate expression and ignore it with the comma operator:

#define C(x) (sizeof(...), (x))

But the problem is that you will get a bunch of warnings about "left-hand operand of comma expression has no effect", even though that's precisely the desired effect.

UPDATE: Multiple readers have pointed out that casting the result of sizeof to void silences the unused warnings (duh!).

GCC wonkiness

Initially, I wasn't using error attribute for my __builtin_constant_p solution, but rather I was using a negative sized array (wrapped inside a struct where VLAs are forbidden) to trigger the compilation error:

#define C(x) ((__typeof__(x)) ((x) + 0*sizeof(  \
    struct { char tmp[__builtin_constant_p(x) ? 1 : -1]; } \
)))

Clang, expectedly errors when the array size is -1. GCC however eats it up, and lets you off the hook with just a warning (double yikes). Hence the error attribute instead (suggested by Arsen) which should be robust against this type of wonkiness.

Conclusion

This turned out to be much bigger rabbit hole than I initially expected. The noisy warnings were also an annoyance, but because I wanted to use this macro in a library, simply turning off the warning wasn't an option for me. And I'd rather keep the library warning free instead of telling the users to switch warnings off.

I'd be interested in knowing if there's any other (hopefully better) solutions that I missed.


UPDATE: u/P-p-H-d points out this solution that uses _Generic, ternary operator and null-pointer constant rules to determine an integer constant expression. Requires C11 and only works for integer, doesn't work for floating point expression unfortunately.

Tags: [ c ]



RSS Feed