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?

1

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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: great idea (the original macro also multiply evaluated its argument)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.