You can get a complete answer to this by combining what @alx and @zneak have, and throwing in __builtin_choose_expr:
#define is_same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_pointer_or_array(p) (__builtin_classify_type(p) == 5)
#define decay(p) (&*__builtin_choose_expr(is_pointer_or_array(p), p, NULL))
#define is_pointer(p) is_same_type(p, decay(p))
So, __builtin_choose_expr(expr, a, b)
is equivalent to expr ? a : b
, except that the types of a
and b
don't need to be compatible, and there are no conversions done. In case the input is not a pointer nor an array, NULL
will be of different type, so is_pointer()
will evaluate to false.
If we use the demo supplied by @Netherwire with a few tweaks, we'll see that the above can't handle incomplete structs (compile error). Still, we get no false-positives while also not failing on non-pointers/arrays. I think this should be good enough for most use-cases.
Here's @Netherwire's demo, with a few modifications:
char c;
printf("c is a pointer: %s\n", is_pointer(c) ? "Yes" : "No");
unsigned long long ll;
printf("ll is a pointer: %s\n", is_pointer(ll) ? "Yes" : "No");
double d;
printf("d is a pointer: %s\n", is_pointer(d) ? "Yes" : "No");
unsigned char *cp;
printf("cp is a pointer: %s\n", is_pointer(cp) ? "Yes" : "No");
struct TM *tp;
printf("tp is a pointer: %s\n", is_pointer(tp) ? "Yes" : "No");
// printf("*tp is a pointer: %s\n", is_pointer(*tp) ? "Yes" : "No"); // error
char a[42];
printf("a is a pointer: %s\n", is_pointer(a) ? "Yes" : "No");
printf("1 is a pointer: %s\n", is_pointer(1) ? "Yes" : "No");
printf("\"str\" is a pointer: %s\n", is_pointer("str") ? "Yes" : "No");
This outputs:
c is a pointer: No
ll is a pointer: No
d is a pointer: No
cp is a pointer: Yes
tp is a pointer: Yes
a is a pointer: No
1 is a pointer: No
"str" is a pointer: No
Huzzah! We have no false-positives, plus the defines were able to handle more than just arrays.