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)) 
asked Jun 8, 2024 at 9:29
5
  • I am looking for a C99-C23 conformant solution, but am not against seeing some GNU extension. (Knowledge is good.) Commented Jun 8, 2024 at 9:33
  • 3
    Note, that macro distinguishes between arrays and pointers, not between arrays and arbitrary other types. Commented Jun 8, 2024 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, 2024 at 10:38
  • @user2357112 Mhm, would IS_POINTER(T) be as simple as !IS_ARRAY(T) then? Commented Jun 9, 2024 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. Commented Jun 10, 2024 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.
answered Jun 9, 2024 at 0:41
Sign up to request clarification or add additional context in comments.

1 Comment

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.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.