Можно ли перебирать аргументы в вариационных макросах?
Мне было интересно, можно ли перебирать аргументы, передаваемые в variadic макрос в C99 или используя какие-либо расширения GCC?
Например, можно ли написать общий макрос, который принимает структуру и ее поля передаются в качестве аргументов и печатает смещение каждого поля в структуре?
Что-то вроде этого:
структурировать { int a; int b; int c; }; / * PRN_STRUCT_OFFSETS напечатает смещение каждого из полей в структуре передается в качестве первого аргумента. * / int main (int argc, char * argv []) { PRN_STRUCT_OFFSETS (struct a, a, b, c); вернуть 0; }
11 ответов
Вот мое домашнее задание дня, оно основано на макро-трюках, и сегодня я особенно узнал о __VA_NARG__
изобрел Лоран Денио. Во всяком случае, следующий пример кода работает до 8 полей для ясности. Просто расширьте код, дублируя, если вам нужно больше (это потому, что препроцессор не имеет рекурсии, поскольку он читает файл только один раз).
#include <stdio.h>
#include <stddef.h>
struct a
{
int a;
int b;
int c;
};
struct b
{
int a;
int b;
int c;
int d;
};
#define STRINGIZE(arg) STRINGIZE1(arg)
#define STRINGIZE1(arg) STRINGIZE2(arg)
#define STRINGIZE2(arg) #arg
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2) arg1##arg2
/* PRN_STRUCT_OFFSETS will print offset of each of the fields
within structure passed as the first argument.
*/
#define PRN_STRUCT_OFFSETS_1(structure, field, ...) printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS_2(structure, field, ...)\
printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
PRN_STRUCT_OFFSETS_1(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_3(structure, field, ...)\
printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
PRN_STRUCT_OFFSETS_2(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_4(structure, field, ...)\
printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
PRN_STRUCT_OFFSETS_3(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_5(structure, field, ...)\
printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
PRN_STRUCT_OFFSETS_4(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_6(structure, field, ...)\
printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
PRN_STRUCT_OFFSETS_5(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_7(structure, field, ...)\
printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
PRN_STRUCT_OFFSETS_6(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_8(structure, field, ...)\
printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
PRN_STRUCT_OFFSETS_7(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_NARG(...) PRN_STRUCT_OFFSETS_NARG_(__VA_ARGS__, PRN_STRUCT_OFFSETS_RSEQ_N())
#define PRN_STRUCT_OFFSETS_NARG_(...) PRN_STRUCT_OFFSETS_ARG_N(__VA_ARGS__)
#define PRN_STRUCT_OFFSETS_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define PRN_STRUCT_OFFSETS_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0
#define PRN_STRUCT_OFFSETS_(N, structure, field, ...) CONCATENATE(PRN_STRUCT_OFFSETS_, N)(structure, field, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS(structure, field, ...) PRN_STRUCT_OFFSETS_(PRN_STRUCT_OFFSETS_NARG(field, __VA_ARGS__), structure, field, __VA_ARGS__)
int main(int argc, char *argv[])
{
PRN_STRUCT_OFFSETS(struct a, a, b, c);
printf("\n");
PRN_STRUCT_OFFSETS(struct b, a, b, c, d);
return 0;
}
который распечатывает:
struct a:a-0
struct a:b-4
struct a:c-8
struct b:a-0
struct b:b-4
struct b:c-8
struct b:d-12
РЕДАКТИРОВАТЬ: Вот немного другая версия, которая пытается быть более общей. FOR_EACH(what, ...)
применяется макрос what
каждому другому аргументу в списке переменных аргументов.
Итак, вам просто нужно определить макрос, который принимает один аргумент, например:
#define DO_STUFF(x) foo(x)
который будет применяться к каждому аргументу в списке. Итак, для вашего типичного примера вам нужно немного взломать, но он все еще остается кратким:
#define PRN_STRUCT_OFFSETS_(structure, field) printf(STRINGIZE(structure)":"STRINGIZE(field)" - offset = %d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(struct a, field)
И вы применяете это так:
FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c);
Наконец, полный пример программы:
#include <stdio.h>
#include <stddef.h>
struct a
{
int a;
int b;
int c;
};
#define STRINGIZE(arg) STRINGIZE1(arg)
#define STRINGIZE1(arg) STRINGIZE2(arg)
#define STRINGIZE2(arg) #arg
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2) arg1##arg2
#define FOR_EACH_1(what, x, ...) what(x)
#define FOR_EACH_2(what, x, ...)\
what(x);\
FOR_EACH_1(what, __VA_ARGS__);
#define FOR_EACH_3(what, x, ...)\
what(x);\
FOR_EACH_2(what, __VA_ARGS__);
#define FOR_EACH_4(what, x, ...)\
what(x);\
FOR_EACH_3(what, __VA_ARGS__);
#define FOR_EACH_5(what, x, ...)\
what(x);\
FOR_EACH_4(what, __VA_ARGS__);
#define FOR_EACH_6(what, x, ...)\
what(x);\
FOR_EACH_5(what, __VA_ARGS__);
#define FOR_EACH_7(what, x, ...)\
what(x);\
FOR_EACH_6(what, __VA_ARGS__);
#define FOR_EACH_8(what, x, ...)\
what(x);\
FOR_EACH_7(what, __VA_ARGS__);
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
#define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__)
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0
#define FOR_EACH_(N, what, x, ...) CONCATENATE(FOR_EACH_, N)(what, x, __VA_ARGS__)
#define FOR_EACH(what, x, ...) FOR_EACH_(FOR_EACH_NARG(x, __VA_ARGS__), what, x, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_(structure, field) printf(STRINGIZE(structure)":"STRINGIZE(field)" - offset = %d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(struct a, field)
int main(int argc, char *argv[])
{
FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c);
printf("\n");
return 0;
}
С риском получить значок археолога, я думаю, что в ответе Грегори, приведенном выше, есть небольшое улучшение, используя технику из Перегрузки макроса по количеству аргументов.
С foo.h:
// Make a FOREACH macro
#define FE_1(WHAT, X) WHAT(X)
#define FE_2(WHAT, X, ...) WHAT(X)FE_1(WHAT, __VA_ARGS__)
#define FE_3(WHAT, X, ...) WHAT(X)FE_2(WHAT, __VA_ARGS__)
#define FE_4(WHAT, X, ...) WHAT(X)FE_3(WHAT, __VA_ARGS__)
#define FE_5(WHAT, X, ...) WHAT(X)FE_4(WHAT, __VA_ARGS__)
//... repeat as needed
#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
#define FOR_EACH(action,...) \
GET_MACRO(__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1)(action,__VA_ARGS__)
// Example
// Some actions
#define QUALIFIER(X) X::
#define OPEN_NS(X) namespace X {
#define CLOSE_NS(X) }
// Helper function
#define QUALIFIED(NAME,...) FOR_EACH(QUALIFIER,__VA_ARGS__)NAME
// Emit some code
QUALIFIED(MyFoo,Outer,Next,Inner) foo();
FOR_EACH(OPEN_NS,Outer,Next,Inner)
class Foo;
FOR_EACH(CLOSE_NS,Outer,Next,Inner)
cpp foo.h генерирует:
Outer::Next::Inner::MyFoo foo();
namespace Outer {namespace Next {namespace Inner {
class Foo;
}}}
Если ваша структура описана с помощью X-Macros, то можно написать функцию или макрос для перебора всех полей структуры и вывести их смещение.
#include <stddef.h> // offsetof macro
//--- first describe the structure, the fields, their types
#define X_FIELDS \
X(int, field1) \
X(int, field2) \
X(char, field3) \
X(char *, field4)
//--- define the structure, the X macro will be expanded once per field
typedef struct {
#define X(type, name) type name;
X_FIELDS
#undef X
} mystruct;
//--- "iterate" over all fields of the structure and print out their offset
void print_offset(mystruct *aStruct)
{
#define X(type, name) printf("offset of %s is %d\n", #name, offsetof(mystruct, name));
X_FIELDS
#undef X
}
//--- demonstrate
int main(int ac, char**av)
{
mystruct a = { 0, 1, 'a', "hello"};
print_offset(&a);
return 0;
}
Решение Григория Пакоша отлично сработало. Но у меня были две незначительные проблемы с этим:
Компилируя с педантичной опцией, я получил предупреждение: "ISO99 требует использования аргументов покоя". Это вызвано аргументами в первом макросе FOR_EACH_1. Удаление их и изменение вызова на FOR_EACH_1 в FOR_EACH_2 сняло это предупреждение.
#define FOR_EACH_1(what, x) #define FOR_EACH_2(what, x, ...)\ what(x); \ FOR_EACH_1(what);
Поскольку я использовал его очень обобщенно, мне иногда приходилось вызывать макрос повтора только с одним аргументом. (Я знаю, что нет смысла повторять пункт 1 раз;)). К счастью, решение этой проблемы было довольно простым. Просто удаляем параметр x из макроса FOR_EACH.
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
Вот полный список с двумя изменениями:
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2) arg1##arg2
#define FOR_EACH_1(what, x) \
what(x)
#define FOR_EACH_2(what, x, ...) \
what(x); \
FOR_EACH_1(what, __VA_ARGS__);
#define FOR_EACH_3(what, x, ...) \
what(x); \
FOR_EACH_2(what, __VA_ARGS__);
#define FOR_EACH_4(what, x, ...) \
what(x); \
FOR_EACH_3(what, __VA_ARGS__);
#define FOR_EACH_5(what, x, ...) \
what(x); \
FOR_EACH_4(what, __VA_ARGS__);
#define FOR_EACH_6(what, x, ...) \
what(x); \
FOR_EACH_5(what, __VA_ARGS__);
#define FOR_EACH_7(what, x, ...) \
what(x); \
FOR_EACH_6(what, __VA_ARGS__);
#define FOR_EACH_8(what, x, ...) \
what(x); \
FOR_EACH_7(what, __VA_ARGS__);
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
#define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__)
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0
#define FOR_EACH_(N, what, ...) CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__)
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
Может быть, использовать varargs в качестве инициализатора массива и перебирать countof(массив)? то есть sizeof(массив)/sizeof(массив [0]). Массив потенциально может быть анонимным массивом C99.
Я не могу придумать другого способа перебора переменных var-макроса, поскольку не знаю, как что-либо сделать с текстом каждого элемента var-arg. Часть var-arg также может быть единственным аргументом, в котором есть запятые, для всего, что вы можете сделать с ним с помощью CPP, AFAIK.
Но вот моя идея перебора переменных var-args:
#define countof(a) ( sizeof(a)/sizeof((a)[0]) )
#define MACRO(fd, format, ...) do { int ar_[] = { __VA_ARGS__ }; \
for(int i=0; i<countof(ar_) ; ++i){ \
fprintf(fd, format, ar_[i]); \
} } while(0)
Это мое решение для этого
наслаждаться
#include <stdlib.h>
#include <stdio.h>
#define ITERATE_OVER_VARADICT_MACROS( str, ...)\
do{\
int i, _arr_[] = {__VA_ARGS__};\
fprintf(stderr,"msg =%s\n", str); \
for(i=0; i<sizeof(_arr_)/sizeof(int) ; i++){ \
fprintf(stderr,"_arr_[%d]= %d\n", i, _arr_[i] ); \
}\
}while(0)
int main(int argc, char* argv[])
{
ITERATE_OVER_VARADICT_MACROS("Test of iterate over arguments in variadic macros", 10,12, 34);
return 0;
}
Это лучшее, что я могу придумать, со стандартным C:
#include <stddef.h>
#include <stdio.h>
// prints a single offset
#define PRN_STRUCT_OFFSET(x, a) printf("&" #x "." #a " = %d\n", offsetof(x, a));
// prints a struct with one member
#define PRN_STRUCT_OFFSETS_1(x, a) PRN_STRUCT_OFFSET(x, a)
// prints a struct with two members
#define PRN_STRUCT_OFFSETS_2(x, a, b) \
PRN_STRUCT_OFFSET(x, a) \
PRN_STRUCT_OFFSET(x, b)
// and so on until some N.
// Boost.Preprocessor might help here, I'm not sure
struct some_struct
{
int a;
void* c;
};
int main(void)
{
PRN_STRUCT_OFFSETS_2(struct some_struct, a, c);
return 0;
}
Я добавляю это как другой ответ. Вот попытка сделать это с C++0x, скомпилированная с g++ 4.5.0
#include <iostream>
using namespace std;
template<typename L>
inline void for_each(L l)
{
}
template<typename L, typename P, typename... Q>
inline void for_each(L l, P arg, Q... args)
{
l(arg);
for_each(l, args...);
}
int main()
{
for_each([] (int x) { cout << x; }, 1, 2, 3);
return 0;
}
Программа печатает
123
Однако при таком подходе все параметры, передаваемые в лямбда-выражение, должны иметь одинаковый тип, int
в приведенном выше примере. Тем не менее, лямбда-выражения позволяют вам захватывать переменные, такие как:
int main()
{
int offset = 10;
for_each([offset] (int x) { cout << offset + x << endl; }, 1, 2, 3);
return 0;
}
который распечатывает:
11
12
13
Чтобы включить пустой
__VA_ARGS__
, можно использовать расширение GNU
##_VA_ARGS__
https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
Поскольку /questions/21669913/mozhno-li-perebirat-argumentyi-v-variatsionnyih-makrosah/21669922#21669922 не всегда хорошо работает для GCC 12, существует улучшенная версия с использованием__VA_OPT__
убрать лишние запятые:
#include <assert.h>
// Make a FOREACH macro
#define FE_0(WHAT)
#define FE_1(WHAT, X) WHAT(X)
#define FE_2(WHAT, X, ...) WHAT(X)FE_1(WHAT, __VA_ARGS__)
#define FE_3(WHAT, X, ...) WHAT(X)FE_2(WHAT, __VA_ARGS__)
#define FE_4(WHAT, X, ...) WHAT(X)FE_3(WHAT, __VA_ARGS__)
#define FE_5(WHAT, X, ...) WHAT(X)FE_4(WHAT, __VA_ARGS__)
//... repeat as needed
#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
#define FOR_EACH(action,...) \
GET_MACRO(__VA_ARGS__ __VA_OPT__(,) FE_5,FE_4,FE_3,FE_2,FE_1,FE_0)(action __VA_OPT__(,) __VA_ARGS__)
#define F(X) _Generic((X), int:1, long: 2, float: 4),
#define CALL(FF, ...) FF((int[]){ FOR_EACH(F, __VA_ARGS__) 0 } __VA_OPT__(,) __VA_ARGS__)
int foo(int* p, ...){ int i = 0; while(*p != 0) { i += *p; ++p; } return i; }
int main()
{
assert(CALL(foo) == 0);
assert(CALL(foo, 1) == 1);
assert(CALL(foo, 1l) == 2);
assert(CALL(foo, 1.f) == 4);
assert(CALL(foo, 1.f, 1) == 5);
assert(CALL(foo, 3l, 1.f, 1) == 7);
}
Рабочий пример: https://godbolt.org/z/q7nbj7PxY
Если вы нацеливаетесь Objective-C
… Ознакомьтесь с УДИВИТЕЛЬНЫМИ KSVarArgs на Github
KSVarArgs - это набор макросов, предназначенных для упрощения работы с переменными аргументами в Objective-C. Все макросы предполагают, что список varargs содержит только объекты target-c или объектоподобные структуры (присваиваемые типу id). Базовый макрос ksva_iterate_list() перебирает аргументы переменной, вызывая блок для каждого аргумента, пока не встретит завершающий ноль. Другие макросы для удобства при преобразовании в общие коллекции.
/*! @param firstNote NSString that is the only known arg
*/
- (void) observeWithBlocks:(NSString*)firstNote,...{
/*! ksva_list_to_nsarray puts varargs into
new array, `namesAndBlocks`
*/
ksva_list_to_nsarray(firstNote, namesAndBlocks);
/// Split the array into Names and Blocks
NSArray *names = [namesAndBlocks subArrayWithMembersOfKind:NSString.class],
*justBlocks = [namesAndBlocks arrayByRemovingObjectsFromArray:names];
[names eachWithIndex:^(id obj, NSInteger idx) {
[self observeName:obj usingBlock:^(NSNotification *n) {
((void(^)())justBlocks[idx])(n);
}];
}];
}
пример использования:
[NSNotificationCenter.defaultCenter observeWithBlocks:
NSViewFrameDidChangeNotification, /// first, named arg
^(NSNotification *m){ [self respondToFrameChange]; }, // vararg
NSTextViewDidChangeSelectionNotification, // vararg
^(NSNotification *z){ [z.infoDict[@"textView"] save]; }, // vararg
nil // must nil-terminate
];