Макрос для замены вложенных циклов
Я нашел этот макрос #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 ответа
Мне наконец удалось написать этот макрос. Я нашел большую часть информации, чтобы сделать это в этой очень хорошей статье ( http://jhnet.co.uk/articles/cpp_magic). Следующие посты (" Можем ли мы иметь рекурсивные макросы?", Есть ли способ использовать строковое преобразование препроцессора C++ для аргументов переменных макросов?, Препроцессор C++ __VA_ARGS__ количество аргументов, трюк макросов Variadic, ...) мне также очень помогают. Этот ответ предназначен для ответа на вопрос. Это не относится к вопросу о макросах и хороших программах программирования. Это другая тема.
Это код
#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 */}
}
Это оказалось сложнее, чем я думал.
Это очень плохая практика, не делайте этого. Другие программисты на Си прекрасно знают циклы 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);
}
РЕДАКТИРОВАТЬ:
В приведенном выше примере тип данных был 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);
}
Это близко следует за решением Лундина, но превращается в нечто более обобщенное.
Для общего пошагового прохождения элементов вы можете оставить свои аргументы как 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);
}