@Lundin shows how to check if a passed expression is an array at compile-time here: Lvalue conversion of _Generic controlling expression involving array clang warning wrong?:
#define IS_ARRAY(T) \
_Generic( &(T), \
typeof(*T) (*)[] : true, \
default : false \
)
Can we further differentiate between an array which has variable length array type and one which does not?
Why do I need this? I have a generic SWAP()
that does not work for VLAs. I would like to check at compile-time (static_assert
) whether a VLA was passed to SWAP()
and guide the user to use another variant of SWAP()
that work for VLAs. That would be more understandable than a compiler error about having a variable-length array in a compound-literal...
For the curious, this is the code:
```c
#include <string.h>
/**
* Like C11's _Static_assert() except that it can be used in an expression.
*
* EXPR - The expression to check.
* MSG - The string literal of the error message to print only if EXPR evalutes
* to false.
*
* Always return 1 (true). */
#define STATIC_ASSERT_EXPR(EXPR, MSG) \
(!!sizeof( struct { static_assert ( (EXPR), MSG ); char c; } ))
/**
* Determines if an expression is compatible with a type.
*
* Expands to 1 (true) if X is compatible with T, else 0 (false). */
#define IS_COMPATIBLE(X, T) \
_Generic((X), \
T: 1, \
default: 0)
[[gnu::always_inline]] static inline void swap_internal(size_t psize,
void *restrict tmp,
void *restrict p1,
void *restrict p2)
{
memcpy(tmp, p1, psize);
memcpy(p1, p2, psize);
memcpy(p2, tmp, psize);
}
/**
* Swaps the contents of A and B.
*
* Properties:
* - It evaluates each of A and B only once.
* - It has a compile-time check for the correct sizes, and prints a nice,
* user-friendly error message if the sizes are different.
* - It has a compile-time check for compatible types, and prints a nice,
* user-friendly error message if the types are incompatible.
* - It has no naming issue with a hidden variable.
* - The size of the temporary variable is computed at compile time, so the
* compound literal is not a dynamic array.
* - It does not rely on VLAs, so it is more portable.
*
* Note: The expressions must allow the & operator to be applicable. Thus it
* will not work on variables that are declared with the register storage class.
* Moreover, it would also not work with VLAs, and would invoke undefined
* behavior if A and B are the same. */
#define SWAP(A, B) \
swap_internal( \
(sizeof (A) * STATIC_ASSERT_EXPR( sizeof (A) == sizeof (B), \
#A " and " #B " must have same size.")), \
(char [ STATIC_ASSERT_EXPR( IS_COMPATIBLE(A, typeof(B)), \
#A " and " #B " must have compatible types") * sizeof (A)] {}), \
&(A), \
&(B))
_Generic
spec says: The type name in a generic association shall specify a complete object type other than a variably modified type.IS_POINTER(T)
be as simple as!IS_ARRAY(T)
then?IS_COMPATIBLE()
in the code is broken, as it would not work correctly for two array types or for an array type and a pointer type.