Макрос для замены вложенных циклов - PullRequest
0 голосов
/ 11 января 2019

Я нашел этот макрос #define TIMES(x) for(int i1=0;i1<x;i1++) очень практичным для сокращения текста кода. Но я не знаю, как написать такой макрос, когда у меня есть вложенные циклы, и даже я не знаю, возможно ли это. Идея заключается в следующем. Можно ли написать этот код

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]) */
}

в

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

с неким "рекурсивным" макросом, который обнаруживает все TIMES и заменяет их на for цикл со счетчиками циклов i1, i2, i3, ... i'n '?

Ответы [ 3 ]

0 голосов
/ 11 января 2019

Это близко следует за решением Лундина , но преобразуется в нечто более обобщенное.

Для общего перехода по элементам вы можете оставить свои аргументы как void *, аналогично qsort и 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);
    }
}

Обратному вызову передается размер элемента. Предполагается, что функция обратного вызова осведомлена о базовом типе объекта, поэтому она может правильно определить, какое измерение пересекается. Например, при обходе int[4][5][6]:

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

И функция печати может выглядеть так:

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);
}

Попробуйте онлайн

0 голосов
/ 13 января 2019

Мне наконец-то удалось написать этот макрос. Я нашел большую часть информации, чтобы сделать это в этой очень хорошей статье (http://jhnet.co.uk/articles/cpp_magic). Следующие сообщения ( Можем ли мы иметь рекурсивные макросы? , Есть ли способ использовать строковое преобразование C ++ препроцессора) на аргументы макроса с переменным числом аргументов? , препроцессор C ++ __VA_ARGS__ количество аргументов , трюк с переменным макросом , ...) мне также очень помогают. Этот ответ предназначен для ответа на вопрос. Это не относится к вопросу о макросах и хороших программах программирования. Это другая тема.

Это код

#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 */}
}

Это оказалось сложнее, чем я думал.

0 голосов
/ 11 января 2019

Это очень плохая практика, не делайте этого. Другие программисты на Си прекрасно знают циклы for, но они совершенно не обращают внимания на ваш секретный макроязык. Кроме того, подобные функциональные макросы имеют плохую безопасность типов и должны использоваться только в качестве крайней меры.

Правильное решение - не использовать макрос, а функцию. Если вы хотите использовать правильное общее программирование, вы можете написать его следующим образом:

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]);
  }
}

Где callback - указатель функции, предоставляемый вызывающей стороной, который содержит фактическую функциональность. Аналогично телу цикла в вашем макросе.

Полный пример:

#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:

В приведенном выше примере тип данных был int. Но так как это общее программирование, вы можете сделать некоторые изменения и поменять их на любой другой тип данных, например, массив или структуру. Подвох в том, что вы должны передать параметр обратному вызову через указатель, а не передавать его по значению. Пример:

#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);
}
...