Как применить функцию к каждому аргументу макроса?
Хочу получить такое поведение, чтобы каждый аргумент макроса был заключен в некоторый код. Например, напечатать каждый аргумент:
#define print_all(...) /* magic */
print_all(1, 2, "123"); // calls std::cout << 1; std::cout << 2; std::cout << "123";
Ответы (2 шт):
В C++ это можно сделать без макросов:
#include <iostream>
struct A {
A(int x) : x(x) { std::cout << "cons a\n"; }
A(const A& a) : x(a.x) { std::cout << "copy cons a\n"; }
int x;
};
std::ostream &operator <<(std::ostream &os, const A &a) {
return os << a.x;
}
void print_all() {}
template<typename Type, typename... Types>
void print_all(Type&& arg, Types&&... args) {
std::cout << std::forward<Type>(arg);
print_all(std::forward<Types>(args)...);
}
int main() {
A a(42);
print_all(1, 2, "123", a);
}
Конструкторы копирования не вызываются и рекурсии нет:
$ g++ -std=c++17 -pedantic -Wall -Wextra -Werror -Wwrite-strings -Wconversion temp.cpp $ ./a.out cons a 1212342
...
movl $.LC0, %esi
movl $_ZSt4cout, %edi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl $1, %esi
movl $_ZSt4cout, %edi
call _ZNSolsEi
movl $2, %esi
movl $_ZSt4cout, %edi
call _ZNSolsEi
movl $3, %edx
movl $.LC1, %esi
movl $_ZSt4cout, %edi
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
leaq 4(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZlsRSoRK1A
...
Уф, поехали. Как обычно, я расчитываю что пользователи MSVC пользуются /std:c++latest /Zc:preprocessor
(или 20
вместо latest
) (работать со старым препроцессором - последнее дело).
Есть три варианта:
Ровно как вы хотите, через макрос. Для этого нужно нагенерировать N однотипных макросов, где N - максимальное число аргументов. Это нужно, поскольку рекурсия в макросах не работает. (Вроде умельцы как-то делают меньше макросов чем N (логарифм от N?), но я бы туда не углублялся.)
#define LOOP(m, d, ...) __VA_OPT__(LOOP_0(m, d, __VA_ARGS__)) #define LOOP_0(m, d, x, ...) m(d, x) __VA_OPT__(LOOP_1(m, d, __VA_ARGS__)) #define LOOP_1(m, d, x, ...) m(d, x) __VA_OPT__(LOOP_2(m, d, __VA_ARGS__)) #define LOOP_2(m, d, x, ...) m(d, x) __VA_OPT__(LOOP_3(m, d, __VA_ARGS__)) // --- #define STEP(d, x) (d,x) LOOP(STEP, 42) // ничего LOOP(STEP, 42, 1) // (42,1) LOOP(STEP, 42, 1,2) // (42,1)(42,2) LOOP(STEP, 42, 1,2,3) // (42,1)(42,2)(42,3)
Обратите внимание на аргумент
d
- чтобы проносить дополнительную информацию в цикл снаружи цикла, если она там нужна (или можно оставить пустым).Этот вариант очень удачно игнорирует запятую после последнего аргумента.
Через макрос, но с другим синтаксисом:
FOO((1)(2)(3))
. Это позволяет не генерировать N однотипных макросов.Тут есть два подвида:
Простой - который не позволяет проносить информацию снаружи в тело цикла (как аргумент
d
выше), зато пишется просто. (И как следствие, этот вариант нельзя один раз абстрагировать в "макрос цикла" как в примере выше, и портянку нужно писать заново для каждого отдельного вида цикла.)#define END(...) END_(__VA_ARGS__) #define END_(...) __VA_ARGS__##_END #define BODY(...) std::cout << __VA_ARGS__; #define LOOP_A(...) BODY(__VA_ARGS__) LOOP_B #define LOOP_B(...) BODY(__VA_ARGS__) LOOP_A #define LOOP_A_END #define LOOP_B_END #define LOOP(...) END(LOOP_A __VA_ARGS__) LOOP() // Ничего LOOP((1)(2)(3)) // std::cout << 1; std::cout << 2; std::cout << 3;
Этот фокус с двумя макросами, вызывающими друг друга, рекурсией не считается, и поэтому работает для любого размера списка (но для синтаксиса (1) это не работает).
Сложный - который позволяет проносить информацию в тело цикла. Через преобразование
(a)(b)(c)
сначала к последовательности видаa)b)c)
. Это откровенно сложно (поэтому полный код не привожу), но достаточно написать один раз.Я это подглядел кое у кого, и оформил в виде библиотеки: https://github.com/HolyBlackCat/macro_sequence_for
Еще, конечно, можно поступить как в пункте (1) и нагенерировать себе N макросов, но непонятно зачем так делать, когда есть предыдущий вариант.
Ну и третий способ - использовать шаблон вместо макроса, как предложили в соседнем ответе. Но там предложили не самый лучший способ это делать: во-первых рекурсия не нужна,
а во-вторых (особенно при рекурсии!) аргументы лучше передавать по ссылке.(уже починили)В общем виде так:
#include <iostream> template <typename ...P> void foo(P &&... params) { (void(std::cout << std::forward<P>(params) << '\n'), ...); } int main() { foo(1,2,"foo"); // 1 2 foo }
Или если аргументы read-only, то проще и лучше так:
template <typename ...P> void foo(const P &... params) { (void(std::cout << params << '\n'), ...); }