This is quite intricate, since:
- A lot of things in C can be integer constant expressions while they are not integer constants. For example
1|1.
- C has a whole lot of ways to express integer constants, including hex, octal and binary notation (C23), various suffixes like
U or L. And as per C23 also decimal separators '.
I'm making the assumption that it is fine that something passed to the macro, which is not an integer constant, should either result in some manner of (likely confusing) compiler error or otherwise cause the macro to return zero.
Some things like false or '1' could arguably be considered integer constants, but they are not typically something we would like to pass such a check either.
I came up with a macro that catches most but not all of the various special cases. It consists of 2 expressions:
(void)(struct{int ident##x;}){}.ident##x. This creates an anonymous struct inside a compound literal. It then tries to name a struct member ident + whatever was passed to the macro.
Which in case of integer constants is fine to place there, since an identifier may not begin with a number but may contain letters and numbers. But in case x contains any pre-processor token which are not letters and numbers, then we will get a compiler error. This will weed out integer constant expressions.
(#x)[0] >= '0' && (#x)[0] <= '9' Inspired by the union trick posted by @CPlus.
This simply checks if the first letter in the pp token is a digit, which is always the case on integer constants. (Hex, octal and binary constants all start with 0).
Forming a macro out of these two (in C23), we put the first expression at the left side of a comma operator to discard it (and cast to void to hush up compiler warnings):
#define IS_INTEGER_CONSTANT(x) ( (void)(struct{int ident##x;}){}.ident##x , \
(#x)[0] >= '0' && (#x)[0] <= '9' )
Test cases:
#include <stdio.h>
#include <stdint.h>
#define IS_INTEGER_CONSTANT(x) ( (void)(struct{int ident##x;}){}.ident##x , \
(#x)[0] >= '0' && (#x)[0] <= '9' )
#define TEST(x) printf("%s is %san integer constant.\n", #x, IS_INTEGER_CONSTANT(x)?"":"NOT ")
int main()
{
TEST(0);
TEST(123);
TEST(0123);
TEST(0x123);
TEST(1UL);
TEST(UINT32_C(1));
TEST( false ); // it's a bool and keyword in C23, not a macro
int i = 1; TEST(i);
typedef int type; TEST(type);
TEST(nullptr);
constexpr int ten = 10; TEST(ten);
//TEST(1'2'3); fails here, C23 integer constants
/* The following are not integer constants and give various compiler errors:
TEST(1|1);
TEST( (char)1 );
TEST('1');
TEST(1.0);
TEST(NULL);
TEST("hello");
*/
}
Output:
0 is an integer constant.
123 is an integer constant.
0123 is an integer constant.
0x123 is an integer constant.
1UL is an integer constant.
UINT32_C(1) is an integer constant.
false is NOT an integer constant.
i is NOT an integer constant.
type is NOT an integer constant.
nullptr is NOT an integer constant.
ten is NOT an integer constant.
So this failed to catch the corner case of C23 ' decimal separators but worked with all other weird corner cases I could come up with.
STRING(10)andSTRING(0xA)map to the same type? If the answer is "Yes", life is going to be hell! You can attempt to make a macro foolproof, but rest assured, a better fool will come along and break it. It may not be worth the mental gymnastics.STRING(0xA)STRING(ten)?STRINGinvoked with an undesired form of argument. Then call the script as part of the build procedure (or even as a commit hook in the source code control system).