On older versions of clang (including macOS 10.10 / Xcode 7.2) and, I believe, GCC this code
#include
#define is_same(T, x) _Generic((x), T: "true", default: "false")
int main(void) {
int arr[5];
int arr2[7];
printf("arr === int[5] -> %s\n", is_same(int[5], arr));
printf("arr === int[7] -> %s\n", is_same(int[7], arr));
printf("arr2 === int[5] -> %s\n", is_same(int[5], arr2));
printf("arr2 === int[7] -> %s\n", is_same(int[7], arr2));
return 0;
}
produces
arr === int[5] -> true
arr === int[7] -> false
arr2 === int[5] -> false
arr2 === int[7] -> true
Unfortunately either clang or GCC (I can't remember) decided the obvious behavior was wrong and changed it so that _Generic behaved as-if array expressions decayed to pointers. The C11 specification for _Generic was insufficiently precise, and for various reasons both vendors and (IIRC) the C committee are going to go with the least common denominator approach (just treat them like pointers) for consistency.
So newer versions of clang and GCC print out all false.
But another way of showing that arrays are real types is with
#include
#define countof(a) (sizeof (a) / sizeof *(a))
int main(void) {
int arr[5];
int arr2[7];
printf("countof(arr) -> %zu\n", countof(arr));
printf("countof(*&arr) -> %zu\n", countof(*&arr));
printf("countof(arr2) -> %zu\n", countof(arr2));
printf("countof(*&arr2) -> %zu\n", countof(*&arr2));
return 0;
}
which produces
countof(arr) -> 5
countof(*&arr) -> 5
countof(arr2) -> 7
countof(*&arr2) -> 7
on all version of clang and GCC, and should on any other conformant C compiler. Although I would think that the simple sizeof proof should suffice to show that arrays are real types, notwithstanding that their evaluation rules are peculiar.
Alas, the disaster with _Generic and array expressions only proves that the situation is less than ideal. Although part of the problem is that _Generic was a novel language feature that didn't fit neatly into the historical translation phases. IMO C++ gets a lot of things wrong about C semantics, but apparently they got decltype right (presuming the behavior is a product of a clearer specification, and that behavior is consistent across implementations).
To be fair, although inelegant the compromise behavior for _Generic makes some sense. The principle use for _Generic is to implement crude function overloading. Because arrays always decay to pointers when passed to functions, it's convenient that _Generic would capture array expressions as pointers. OTOH, it makes some useful behaviors impossible. And the convenient behavior could have been had by manually coercing arrays to pointers using a trick like:
#define decay(arr) ((0)? 0 : (arr))
#define is_same(T, x) _Generic(decay(x), ...)