2

I found this macro #define TIMES(x) for(int i1=0;i1<x;i1++)very pratical to shorten the code text. But I do not know how to write such a macro when I have nested loops and even I do not know if it is possible. The idea is the following. Is it possible to write this code

for(int i1=0;i1<5;i1++)
    for(int i2=0;i2<3;i2++)
        for (int i3=0;i3<7;i3++)
        /* many nested `for` loops */
{
    /* some code, for example to print an array printf("%d \n",a[i1][i2][i3]) */
}

as

TIMES(5) TIMES(3) TIMES(7) ....
{
    /* some code, for example to print an array printf("%d \n",a[i1][i2][i3]) */
}

with a sort of "recursive" macro that detects all TIMES and replaces them by a forloop with i1, i2, i3, ... i'n' loop counters ?

jxh
  • 69,070
  • 8
  • 110
  • 193
Stef1611
  • 1,978
  • 2
  • 11
  • 30

3 Answers3

6

This is very bad practice, don't do this. Other C programmers are perfectly aware of for loops, but they are completely oblivious to your private, secret macro language. In addition, function-like macros like these have poor type safety and should only be used as the last resort.

The correct solution is not to use a macro, but a function. If you wish to utilize proper generic programming, you could write it as follows:

typedef void callback_t (int data);

void traverse (size_t n, int data[n], callback_t* callback)
{
  for(size_t i=0; i<n; i++)
  {
    callback(data[i]);
  }
}

Where callback is a function pointer provided by the caller, which contains the actual functionality. Similar to the loop body in your macro.

Full example:

#include <stdio.h>

typedef void callback_t (int data);

void traverse (size_t n, int data[n], callback_t* callback)
{
  for(size_t i=0; i<n; i++)
  {
    callback(data[i]);
  }
}


void print (int i)
{
  printf("%d ", i);
}

int main (void)
{
  int array [5] = {1, 2, 3, 4, 5};

  traverse(5, array, print);
}

EDIT:

In the above example, the data type was int. But since it is generic programming, you can do some tweaks and swap it for any other data type, such as an array or a struct. The catch then is that you must pass the parameter to the callback through a pointer, instead of passing it by value. Example:

#include <stdio.h>

/* Generally it is bad practice to hide arrays behind typedefs like this. 
   Here it just done for illustration of generic programming in C. */
typedef int data_t[3]; 

typedef void callback_t (data_t* data);

void traverse (size_t n, data_t data[n], callback_t* callback)
{
  for(size_t i=0; i<n; i++)
  {
    callback(&data[i]);
  }
}


void print_array (int(*array)[3])
{
  int* ptr = *array;
  printf("{%d %d %d}\n", ptr[0], ptr[1], ptr[2]);
}

int main (void)
{
  int array [2][3] = { {1, 2, 3}, {4, 5, 6} };

  traverse(2, array, print_array);
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
1

This closely follows Lundin's solution, but is refactored into something more generalized.

To generically step through elements, you can leave your arguments as void *, similar to qsort and bsearch.

typedef void cb_type (void *base, size_t sz);

void
traverse (void *base, size_t n, size_t sz, cb_type *cb) {
    char *p = base;
    for (size_t i = 0; i < n; ++i) {
        cb(p + i*sz, sz);
    }
}

The callback is passed the sizeof of the element. The callback function is supposed to be aware of the underlying type of the object, so it can properly deduce which dimension is being traversed. For example, if traversing a int[4][5][6]:

    int array[4][5][6];
    traverse(array, 4, sizeof(*array), print_456);

And the print function could look like this:

void
print_456 (void *base, size_t sz) {
    if (sz == 5 * 6 * sizeof(int)) {
        traverse(base, 5, 6*sizeof(int), print_456);
        puts("");
    } else if (sz == 6 * sizeof(int)) {
        traverse(base, 6, sizeof(int), print_456);
        puts("");
    } else
        printf("%d ", *(int *)base);
}

Try It Online

jxh
  • 69,070
  • 8
  • 110
  • 193
  • BTW, for macros, you should follow advice given earlier to pass in the indexing variable name. That gives you a lot better control. Macros do allow you to write more terse code, but that is a double edged sword. Generally speaking, you should try to make your code take less time to comprehend than less time to type. – jxh Jan 11 '19 at 10:23
1

I finally succeed in writing this macro. I found most of the informations to do that in this very good article (http://jhnet.co.uk/articles/cpp_magic). The following posts (Can we have recursive macros?, Is there a way to use C++ preprocessor stringification on variadic macro arguments?, C++ preprocessor __VA_ARGS__ number of arguments, Variadic macro trick, ...) help me also a lot. This answer is intended to answer question. It does not address the question of macro and good programming pratices. It is another subject.

This is the code

#define SECOND(a, b, ...) b
#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()
#define BOOL(x) NOT(NOT(x))

#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)
#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...)             _IF_0_ELSE
#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__

#define EMPTY()

#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__

#define DEFER1(m) m EMPTY()
#define DEFER2(m) m EMPTY EMPTY()()

#define FIRST(a, ...) a
#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0

#define MAP(m, first, ...)           \
  m(first,__VA_ARGS__)                           \
  IF_ELSE(HAS_ARGS(__VA_ARGS__))(    \
    DEFER2(_MAP)()(m, __VA_ARGS__)   \
  )(                                 \
    /* Do nothing, just terminate */ \
  )
#define _MAP() MAP

#define PP_NARG(...) \
         PP_NARG_(,##__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
          z,_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0

#define TIMES(...) EVAL(MAP(TIME2FOR,__VA_ARGS__))

#define TIME2FOR(x,...) \
        for(int CAT(i,PP_NARG(__VA_ARGS__))=0; \
            CAT(i,PP_NARG(__VA_ARGS__))<x; \
            CAT (i,PP_NARG(__VA_ARGS__))++)

main() {

int a[3][2][4];
TIMES(3,2,4) a[i2][i1][i0]=i2*100+i1*10+i0;
TIMES (3,2,4) printf("a[%d][%d][%d] : %d\n",i2,i1,i0,a[i2][i1][i0]);
TIMES (3,2,4) {/* whatever you want : loop indexes are ...,i2,i1,i0 */}
}

It turned out to be more difficult than I thought it would be.

Stef1611
  • 1,978
  • 2
  • 11
  • 30