Search code examples
carraysmacroscompile-time

Assign an array or an integer without knowing its nature in the function code (but compiler knows)


I'm looking for something like this snippet. I expect it to know, at compile time, wether it is dealing with an array or not, and avoid the following errors.

#include <stdio.h>


#define IS_ARRAY(x,type) _Generic((&x),          \
                                 type (*)[]: 1,  \
                                 default:   0)

#define GENERIC_ASSIGN(arg,type) if(IS_ARRAY(arg,type)){arg[0] = 1; arg[1] = 2;}else{arg = 2;}

int main(void)
{

    int foo = 0;
    int bar[10] = {0};

    GENERIC_ASSIGN(bar,int); //-->  error: assignment to expression with array type
    GENERIC_ASSIGN(foo,int); //--> error: subscripted value is neither array nor pointer nor vector  "arg[0] = 1; arg[1] = 2;"

    return 0;
}

When I do write GENERIC_ASSIGN(bar,int) i do know that 'bar' is an ARRAY, so does the compiler.

See this topic that explains one part of the problem here

The problem would have been solved easily if '#if' were allowed inside macros


Solution

  • You can't assign to arrays, so you will have to use memcpy. For example, have the macro create a compound literal of all initializers and then memcpy that one.

    #include <stdio.h>
    #include <string.h>
    
    #define IS_ARRAY(x,type) _Generic((&x),                             \
                                     type (*)[]: 1,                     \
                                     default:    0)
    
    #define INIT(arg, type, ...) memcpy(&(arg),                         \
                                        (type[]){__VA_ARGS__},          \
                                        sizeof((type[]){__VA_ARGS__})) 
    
    #define GENERIC_ASSIGN(arg,type) IS_ARRAY(arg,type) ?               \
                                     INIT(arg,type,1,2) :               \
                                     INIT(arg,type,2)
    
    int main(void)
    {
      int foo = 0;
      int bar[10] = {0};
    
      GENERIC_ASSIGN(bar,int);
      GENERIC_ASSIGN(foo,int);
    
      printf("%d %d\n",bar[0], bar[1]);
      printf("%d\n",foo);
    
      return 0;
    }
    

    Notably, with this method it doesn't really matter what type you use, array or not. The size of the initializer list is all that matters.

    gcc -O2 resolves this into a couple of register loads (x86):

        mov     edx, 2
        mov     esi, 1
        xor     eax, eax
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        mov     esi, 2
        mov     edi, OFFSET FLAT:.LC1
        xor     eax, eax
        call    printf