Search code examples
cfunctionmacrostype-safetyvariadic

Macro function to avoid sizeof boilerplate during function call


I declared a function with the following signature (implementation is simplified):

#include <stdio.h>

struct test_s{
    int a, b;
};

void foo(struct test_s **out, size_t *szs, size_t arr_len){
    for(size_t i = 0; i < arr_len; i++){
        for(size_t j = 0; j < szs[i]; j++){
            struct test_s ts = out[i][j];
            printf("a = %d; b = %d\n", ts.a, ts.b);
        }
    }
}

In case a caller uses arrays to be adjusted to pointers it can be called as follows:

int main(void){
    struct test_s a1[] = {{0, 1}, {2, 3}, {4, 5}};
    struct test_s a2[] = {{4, 6}};
    foo((struct test_s *[]){a1, a2}, 
        (size_t[]){sizeof a1 / sizeof(struct test_s), sizeof a2 / sizeof(struct test_s)}, 
        2);
}

As can be seen the function call looks complicated, error-prone and hard to read.

When it comes to using 3 arguments things get worse:

int main(void){
    struct test_s a1[] = {{0, 1}, {2, 3}, {4, 5}};
    struct test_s a2[] = {{4, 6}};
    struct test_s a3[] = {{2, 3}, {4, 5}};
    foo((struct test_s *[]){a1, a2, a3},
        (size_t[]){sizeof a1 / sizeof(struct test_s), sizeof a2 / sizeof(struct test_s), sizeof a3 / sizeof(struct test_s)},
        3);
}

So it would be perfect to implement it as macro when it comes to arrays. It is pretty straightforward to implement it as follows:

#define FOO_ARR_2(a1, a2) \
    do{ \
        foo((struct test_s *[]){a1, a2},  \
        (size_t[]){sizeof a1 / sizeof(struct test_s), sizeof a2 / sizeof(struct test_s)}, \
        2);\
    } while(0)

I see 2 problems with such a macro:

  1. I would have to define FOO_ARR_3, FOO_ARR_4, etc...
  2. Lack of type safety. In case a caller pass something different then struct test_s[]

QUESTION: Would it be possible to implement it as a variadic macro function like #define FOO_ARR(...)?


Solution

  • Instead of complicating things even more by wrapping the complicate initialisation into a complicated (if even possible) variadic macro, just declare the function in question as a variadic one itself.

    This might look like this:

    #include <stdio.h>
    #include <stdarg.h>
    
    /* expects: number-of-arrays followed by 
       number-of-arrays tuples {arrays-size,  pointer to array's 1st element} */
    
    struct test_s{
      int a, b;
    };
    
    void foo(size_t arr_len, ...)
    {
      va_list vl;
    
      va_start(vl, arr_len);
    
      for (size_t i = 0; i < arr_len; ++i) 
      {
        size_t s = va_arg(vl, size_t);
        struct test_s * p = va_arg(vl, struct test_s *);
    
        for (size_t j = 0; j < s; ++j)
        {
          struct test_s ts = p[j];
          printf("a = %d; b = %d\n", ts.a, ts.b);
        }
      }
    
      va_end(vl);
    }
    

    Use it like this:

    struct test_s{
      int a, b;
    };
    
    void foo(size_t, ...);
    
    int main(void)
    {
      /* using two arrays: */
      {
        struct test_s a1[] = {{0, 1}, {2, 3}, {4, 5}};
        struct test_s a2[] = {{4, 6}};
    
        foo(2, 
          sizeof a1 / sizeof *a1, a1, 
          sizeof a2 / sizeof *a2, a2
        );
      }
    
      /* using three arrays: */
      {
        struct test_s a1[] = {{0, 1}, {2, 3}, {4, 5}};
        struct test_s a2[] = {{4, 6}};
        struct test_s a3[] = {{2, 3}, {4, 5}};
    
        foo(3, 
          sizeof a1 / sizeof *a1, a1, 
          sizeof a2 / sizeof *a2, a2,
          sizeof a3 / sizeof *a3, a3
        );
      }
    }