I'm trying to do a macro like this:
#define STRING(i) \
struct STRING##i \
{ \
size_t len; \
char chars[i]; \
}
but the problem is this works with constexpr
arguments like this:
constexpr int ten = 10;
STRING(ten) mystr;
I don't want that because then STRING(ten)
and STRING(10)
aren't compatible types and that might confuse users of this macro.
I tried the following:
#define STRING(i) \
struct STRING##i \
{ \
_Static_assert( CAT(i, ul) , "must be nonzero literal"); \
size_t len; \
char chars[i]; \
}
it appends ul
to the literal and makes it an unsigned long literal, but fails with non-literals since it makes it a different identifier. but the problem with this is if the user has another constexpr
named tenul
for example.
I wonder if there's a better way to make this macro fail unless an integer literal is provided.
To add to the accepted answer, I did:
#define STRING(i) \
struct STRING##i \
{ \
_Static_assert((1##i##1ul || i##8ul || 1), "argument must be positive decimal integer literal"); \
size_t len; \
char chars[i]; \
}
This makes sure octals and hexadecimals aren't accepted, only decimal literals.
Valid identifiers start with alphabetic characters or underscores. Valid integer literals start with digits. If only there was a way to check if the first character of #i
(the macro argument converted to a string) was numeric at compile time. But as far as I can tell using (#i)[0]
only works at runtime.
The question is tagged c23 so here is the only method I could think of and only works in C23:
In 6.6 Constant Expressions:
Starting from a structure or union constant, the member-access . operator may be used to form a named constant or compound literal constant as described above.
This means in theory you should be able to have code along the lines of:
#define IS_LITERAL(i)\
_Static_assert(((constexpr union {unsigned char f, s[sizeof(#i)];}){.s = #i}).f - '0' <= 9,\
"Not numeric literal");
What this does is creates a constant anonymous union containing a char
field and a char[]
field and uses the .
operator to get the char
field which will be the first character of the char[]
field and then checks if the character is numeric.
Edit: The above technically invokes undefined behavior/is not required to compile as per the following rule:
If the member-access operator . accesses a member of a union constant, the accessed member shall be the same as the member that is initialized by the union constant's initializer.
Alternatively if you want a compile time error for a non literal integer value and do not mind losing the ability to use hexadecimal constants you could use:
#define IS_LITERAL(i) _Static_assert(1##i || 1); // Or int dummy = 1##i; or similar
If i
were an identifier then i
preceded by a 1 would not be a valid identifier name but prepending 1 to an octal or decimal constant would still be a valid expression.
Note: 1##i##0
can also be used to block identifiers such as l
or ul
.