Reputation: 13948
static_assert()
is a pretty great capability available since C11.
For pre-C11 compilers though, this capability must be emulated. It's not too hard, there are many examples available over Internet.
For example :
#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert_##MSG[(CONDITION)?1:-1]
This makes it possible to transfer an error message in the condition, which is handy to explain what's going wrong if it ever gets triggered.
However, this MSG
is a lot different from the one in C11
's static_assert()
:
This is so different from C11
's static_assert()
that it seems impossible to create a macro which would switch transparently between the C11
and the C90
version depending on the compiler.
In an effort to accept an error message which "looks like C11
", aka a string with double quote, I've tested a new macro :
#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert[((void)(MSG), ((CONDITION)?1:-1))]
Using the ,
comma operator, this macro should accept MSG
as a string, and just disregard it. But it will be displayed in case of error, which is the intention.
It works fine on clang
, but not of gcc
: error: variably modified at file scope
.
I'm trying to understand why, and if there is a work around
Upvotes: 3
Views: 2523
Reputation: 4283
One-liner
#define STATIC_ASSERT(CONDITION, MSG) { typedef char test[(CONDITION)?1:-1]; (void)(test*) #MSG; } (void)0
Upvotes: 1
Reputation: 9962
If you replace the typedef-array-trick with the enum-trick, then you will get something that seems to work with both clang and gcc:
#define CONDITION 1
#define TOKENPASTE(a, b) a ## b // "##" is the "Token Pasting Operator"
#define TOKENPASTE2(a,b) TOKENPASTE(a, b) // expand then paste
#define static_assert(x, msg) enum { TOKENPASTE2(ASSERT_line_,__LINE__) \
= 1 / (msg && (x)) }
static_assert( CONDITION, "This should pass");
static_assert(!CONDITION, "This should fail");
This gives me, with gcc for example, on line 9 of foo.c:
foo.c:9: warning: division by zero [-Wdiv-by-zero]
static_assert(!CONDITION, "This should fail");
^
foo.c:9: error: enumerator value for 'ASSERT_line_9' is not an integer constant
static_assert(!CONDITION, "This should fail");
^~~~~~~~~~~~~
(Here the gcc switch -ftrack-macro-expansion=0
is used, as the extra error messages are not that helpful and just add noise.)
Note that some mangling of the name is still necessary, which you omitted. Here the text ASSERT_line_
is combined with the variable __LINE__
. This ensures a unique name, provided:
ASSERT_line_9
elsewhere.For header files, you will need to add somewhere a single word with only identifier characters. For example:
#define static_assert3(x, msg, file) enum { TOKENPASTE2(file,__LINE__) = \
1 / (msg && (x)) }
#define static_assert(x, msg) static_assert3(x, msg, my_header_h_)
If this fails on line 17, gcc will give an error such as:
error: enumerator value for 'my_header_h_17' is not an integer constant
An alternative for the mangling in header files is to replace __LINE__
with __COUNTER__
. I've not used it, because it is non-standard and because clang was slow to adopt it. But now it has been in gcc, msvc, and clang for about five years.
You could try the same modification with your typedef-array idea, and replace the comma operator with &&
. Then your gcc error changes into a warning. For example, modifying your godbolt example to:
typedef char static_assert_2["hello world!" && (CONDITION) ? 1 : -1];
gives the unwanted warning: variably modified 'static_assert_2' at file scope
for gcc.
Upvotes: 2