cmacrosliteralscompile-timec23

How to check if a macro argument is an integer literal in C


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.


Edit

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.


Solution

  • 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 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.