10

Within C11/gnuC11 is it possible to write a macro that returns an integer constant expression of value 1 or 0 respectively if the macro argument is or isn't a type name or at least a macro can distinguish between integer constant expressions and typenames (i.e., if can detect the argument isn't one of these, it can assume it is the other)?

#define IS_TYPENAME(X) /*???*/ 
_Static_assert( IS_TYPENAME(int), "" );
_Static_assert( !IS_TYPENAME(42), "" );

Motivation:

My motivation was to wrap _Alignas with a macro that would simply do nothing if the suggested alignment (either type or integer expression) was less than the current one (normal _Alignas with a smaller alignment results in an error) and so I wanted to also accept either a typename or an integer expr, but now I'm thinking simply requiring an integer expr (which you can always get from a typename by applying _Alignof) will be the simpler/clearer way to go.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • Not as far as I know. That would be useful, though. – S.S. Anne May 15 '19 at 12:11
  • 2
    As usual I'm very interested about why you need to do this? What is the real underlying problem you need to solve? Why do you think this is the natural solution to that problem? You're sure there's no other way to solve that problem? – Some programmer dude May 15 '19 at 12:11
  • 5
    I donlt think that is possible because a macro is pre-processing, while typenames only become known during compilation. – Paul Ogilvie May 15 '19 at 12:12
  • 2
    You can use `_Generic` but it ain't pretty. – Bathsheba May 15 '19 at 12:12
  • @PaulOgilvie I don't need an integer constant expression usable in `#if` conditionals. – Petr Skocik May 15 '19 at 12:14
  • I've found what I think is a good duplicate which shows you how to do this. If you think this is worth a stand-alone answer then please vote to reopen or ping me. – Bathsheba May 15 '19 at 12:14
  • 2
    @Bathsheba - given `typedef int myInt;` How does `_generic` help to determine if `myInt` is a type name before compilation? – ryyker May 15 '19 at 12:16
  • 3
    If you use an identifier in a context where the compiler expects a type, but it is not a type. the compiler will diagnose an error. That's it's job (among other things). Since all you're doing with you hypothetical macro is a `_Static_Assert`, you're not achieving anything different. So this strikes me as pointless - even if it was possible, which it doesn't appear to be. – Peter May 15 '19 at 12:17
  • @Bathsheba - what is closed? (You have referenced some duplicate, but given no link.) – ryyker May 15 '19 at 12:19
  • @ryyker: I hammered it closed, incorrectly, some good soul has reopened it. – Bathsheba May 15 '19 at 12:21
  • This was the reference: https://stackoverflow.com/questions/9804371/syntax-and-sample-usage-of-generic-in-c11/17290414#17290414 – Bathsheba May 15 '19 at 12:21
  • @ryyker This was the proposed duplicate: https://stackoverflow.com/questions/9804371/syntax-and-sample-usage-of-generic-in-c11 I don't think it is one so I reopened it :P. (I'm open to counter arguments.) – Petr Skocik May 15 '19 at 12:21
  • 1
    @Peter My motivation was to wrap `_Alignas` with a macro that would simply do nothing if the suggested alignment (either type or integer expression) was less than the current one (normal `_Alignas` with a smaller alignment results in an error) and so I wanted to also accept either a typename or an integer expr, but now I'm thinking simply requiring an integer expr (which you can always get from a typename by applying `_Alignof`) will be the simpler/clearer way to go. – Petr Skocik May 15 '19 at 12:26
  • 1
    Took me some time scratching my head, but it seems quite possible to do this, in compile-time, with pure standard C. Answer below. – Lundin May 17 '19 at 10:57

1 Answers1

4

In order to do this, you will need to check if the parameter is of type integer, and you need to check if it is a type or an expression.


Checking if a macro parameter, that may be a type or an expression, is of integer type:

This can be done with _Generic. A _Generic expression cannot contain two types that are identical, so it should suffice if you compare against all the stdint.h types only. Since these will alias with the default integer types, but not collide with each other (like for example int and long might).

Now _Generic doesn't accept a type as operand, so you have to tweak the input to always become an expression.

The trick that I invented just now, is to use the ambiguity between the parenthesis operator and the cast operator, and at the same time use the ambiguity between unary + and binary + operators.

Given (x)+0.

  • If x is a type, () becomes the cast operator and +0 is the unary addition operator applied to an integer constant.
  • If x is an expression, it will get parenthesized and then + is the binary addition operator.

So you can do:

#define IS_INT(x) _Generic((x)+0, \
  uint8_t:  1, int8_t:  1,        \
  uint16_t: 1, int16_t: 1,        \
  uint32_t: 1, int32_t: 1,        \
  uint64_t: 1, int64_t: 1,        \
  default: 0)

This will work for all integer, character and float types, as well as pointers. It will not work on struct/union types (compiler error). It will not work with void* and probably not with NULL (compiler error, can't do pointer arithmetic).


Checking if a macro parameter, that may be a type or an expression, is an expression:

This can also be done using the same trick as above, use the ambiguity between different operators. For example:

#define IS_EXPR(x) (!!(x) + !(x) + 1 == 2)
  • If x is a non-zero integer constant expression, we get 1 + 0 + 1 = 2.
  • If x is a zero integer constant expression, we get 0 + 1 + 1 = 2.
  • If x is a type, we get !!(int)+!(int)+1 which equals 0. Both + are unary.

This doesn't make a difference between float and integers though, so we need to combine this trick with the IS_INT macro.


Solution:

#define IS_INTCONSTEXPR(x) ( IS_INT(x) && IS_EXPR(x) )

Complete example with test cases, printing 1 if integer constant expression, otherwise 0:

#include <stdint.h>
#include <stdio.h>

#define IS_INT(x) _Generic((x)+0, \
  uint8_t:  1, int8_t:  1,        \
  uint16_t: 1, int16_t: 1,        \
  uint32_t: 1, int32_t: 1,        \
  uint64_t: 1, int64_t: 1,        \
  default: 0)

#define IS_EXPR(x) (!!(x) + !(x) + 1 == 2)

#define IS_INTCONSTEXPR(x) ( IS_INT(x) && IS_EXPR(x) )


#define test(arg) printf("%d %s\n", IS_INTCONSTEXPR(arg),(#arg))

int main (void)
{
  test(42);
  test(sizeof(int));
  test(1+1);
  test(int);
  test(unsigned int);
  test(42.0);
  test(double);
  test(uint32_t);
  test(uint32_t*);
  test(_Bool);

  _Static_assert( !IS_INTCONSTEXPR(int), "" ); // OK, passed
  _Static_assert( IS_INTCONSTEXPR(42), "" );   // OK, passed

  return 0;
}

Output:

1 42
1 sizeof(int)
1 1+1
0 int
0 unsigned int
0 42.0
0 double
0 uint32_t
0 uint32_t*
0 _Bool
Lundin
  • 195,001
  • 40
  • 254
  • 396