It turns out there is a way!
Although locally distributed arrays are allowed to have variable lengths in C, the standard explicitly requires such arrays to not have an explicit initializer. We can forcefully disable the VLA language function by providing the array with a list of initializers, which will cause the size of the array to be an integer constant expression (compile-time constant):
int arr[(expression)] = { 0 };
The contents of the initializer does not matter; { 0 } will always work.
This is still slightly inferior to the enum solution because it requires an instruction and enters a name. But, unlike enumerations, arrays can be made anonymous (as compound literals):
(int[expression]){ 0 }
Since a compound literal has an initializer as part of the syntax, there is no way for it to be a VLA, so it is still guaranteed for it that expression will be a compile-time constant.
Finally, since anonymous arrays are expressions, we can pass them to sizeof , which gives us the path to the original value of expression :
sizeof((char[expression]){ 0 })
This has the added bonus of ensuring that the array will never be allocated at runtime.
Finally, with a bit more packaging, we can even handle zero or negative values:
sizeof((char[(expression)*0+1]){ 0 }) * 0 + (expression)
This ignores the actual value of expression when setting the size of the array (which will always be 1), but still considers its constant status; it also ignores the size of the array and returns only the original expression, so the restrictions on the size of the array β must be greater than zero β need not be applied to the return value. expression duplicated, but for what macros (and if it is compiled, it will not be recounted, because a. it is constant, and b) the first use is within sizeof ). In this way:
#define assert_constant(X) (sizeof((char[(X)*0+1]){ 0 }) * 0 + (X))
For bonus points, we can use a very similar method to implement the static_switch expression, combining the array sizes with C11 _Generic (this probably does not have a lot of practical applications, but can replace some cases of nested trojans that are not popular):
#define static_switch(VAL, ...) _Generic(&(char[(VAL) + 1]){0}, __VA_ARGS__) #define static_case(N) char(*)[(N) + 1] char * x = static_switch(3, static_case(0): "zero", static_case(1): "one", static_case(2): "two", default: "lots"); printf("result: '%s'\n", x);
(We take the address of the array to create an explicit pointer type to the array, rather than letting the implementation decide whether _Generic arrays support pointers or not.)
This is slightly more restrictive than assert_constant because it will not allow negative values. Assuming +1 both in the control expression and in all case values, we can at least let it take zero.