1

I was interested to write a type validation macro, which only gives a warning if the type isn't an int/short/long or a pointer.

The trouble I have with this is that a pointer can be any kind of pointer.

#define INT_OR_POINTER_AS_UINTPTR(v) _Generic((v), \
    signed long:  (uintptr_t)(v),            unsigned long:  (uintptr_t)(v), \
    signed int:   (uintptr_t)(v),            unsigned int:   (uintptr_t)(v), \
    signed short: (uintptr_t)(v),            unsigned short: (uintptr_t)(v), \
    default: (((void)(0 ? (*(v)) : 0),                       (uintptr_t)(v))))

The first block is to allow int/short/long

The default case is to allow any pointer.

The intention of (0 ? (*(v)) : 0) is to cause a compiler error if v is not a pointer, but otherwise not effect the generated code (hance the 0 ? ...).

This way, accidental inplicit casts from other types such as float or bool won't go un-noticed.

Ideally, this would work.

int a = 4;
struct Foo *b = NULL;

uintptr_t test_a = INT_OR_POINTER_AS_UINTPTR(a);
uintptr_t test_b = INT_OR_POINTER_AS_UINTPTR(b);

Ideally, and this would fail for both uses.

float a = 4;
struct Foo b = {0};

uintptr_t test_a = INT_OR_POINTER_AS_UINTPTR(a);
uintptr_t test_b = INT_OR_POINTER_AS_UINTPTR(b);

However, even when an int/long/short is given as an argument, The code which checks a pointer is evaluated and errors: invalid type argument of unary '*' (have 'int')

Without having to explicitly enumerate every kind of pointer type which might be passed to this _Generic is there a way to catch all kind of pointers, without evaluating the expression for other (non-pointer) values?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • possible duplicate of [Incompatible pointer types passing in \_Generic macro](http://stackoverflow.com/questions/24743520/incompatible-pointer-types-passing-in-generic-macro) – J. C. Salomon Jan 14 '15 at 18:27

1 Answers1

2

_Bool is a "standard unsigned integer type", see 6.2.5, so if you want special handling for that, you'll need to do it separately.

Otherwise, there's a simple trick you can use. Integers and pointers have the property that the symmetric difference is an integer. So we use the symmetric difference in a context that can only accept an integer, like array subscripting:

#define INT_OR_POINTER_AS_UINTPTR(v) \
             ((sizeof "Allow only integer and pointer types"[(v)-(v)]), (uintptr_t)(v))

The subtraction will fail for struct types, and floating-point types will yield a floating-point difference, which can't be used for pointer arithmetic.

To reject _Bool you can use a _Generic.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    This has the disadvantage to evaluate `v` three times, which is a "no no!" for macros. You could pack the first part into a `sizeof` to avoid this. – Jens Gustedt Jan 18 '15 at 17:32
  • 1
    @Jens: great idea (the original macro also multiply evaluated its argument) – Ben Voigt Jan 18 '15 at 17:33