4

@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))                               
5
  • I am looking for a C99-C23 conformant solution, but am not against seeing some GNU extension. (Knowledge is good.)
    – Harith
    Commented Jun 8 at 9:33
  • 3
    Note, that macro distinguishes between arrays and pointers, not between arrays and arbitrary other types. Commented Jun 8 at 9:55
  • 2
    The _Generic spec says: The type name in a generic association shall specify a complete object type other than a variably modified type. Commented Jun 8 at 10:38
  • @user2357112 Mhm, would IS_POINTER(T) be as simple as !IS_ARRAY(T) then?
    – Harith
    Commented Jun 9 at 14:00
  • Note: The 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.
    – Harith
    Commented Jun 10 at 7:55

1 Answer 1

5

C 2024 draft N3096 6.7.6.2 6 specifies two array types are compatible if they have compatible element types and, if both have integer constant size specifiers, have the same size. Note that if either is a variable length array, this criterion for compatibility does not require the sizes to be the same. This means a variable length array is compatible with a constant length array.

Observe that a variable length array will be compatible with arrays of two different constant lengths, but a constant length array can be compatible with at most one. So we can distinguish a variable length array from a constant length array by testing against two different constant lengths.

Note that that this fails to distinguish an array whose length is not specified, as it is also compatible with any constant length array.

This code:


#include <stdbool.h>

#define IsCompatibleWithArrayOfLengthN(X, N) \
    _Generic(&(X), typeof(*X) (*)[N]: true, default: false)

#define IsArrayOfNonconstantLength(X) \
    (   IsCompatibleWithArrayOfLengthN(X, 1) \
     && IsCompatibleWithArrayOfLengthN(X, 2))


#define Demo(X) \
    printf(#X " %s an array of unspecified or variable length.\n", \
        IsArrayOfNonconstantLength(X) ? "is" : "is not")


#include <stdio.h>


int main(void)
{
    int x = 1;

    int *P;
    int A1[1];
    int A2[2];
    int A3[3];
    extern int ULA[];
    int VLA[x];

    Demo(P);
    Demo(A1);
    Demo(A2);
    Demo(A3);
    Demo(ULA);
    Demo(VLA);
}

produces this output:

P is not an array of unspecified or variable length.
A1 is not an array of unspecified or variable length.
A2 is not an array of unspecified or variable length.
A3 is not an array of unspecified or variable length.
ULA is an array of unspecified or variable length.
VLA is an array of unspecified or variable length.
1
  • Wonderful. So to conclude, it is possible to differentiate between variable-length arrays and constant-length arrays, but not between a variable-length arrays and unspecified-length arrays.
    – Harith
    Commented Jun 9 at 16:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.