Как применить функцию к каждому аргументу макроса?

Хочу получить такое поведение, чтобы каждый аргумент макроса был заключен в некоторый код. Например, напечатать каждый аргумент:

#define print_all(...) /* magic */

print_all(1, 2, "123"); // calls std::cout << 1; std::cout << 2; std::cout << "123";

Ответы (2 шт):

Автор решения: Stanislav Volodarskiy

В 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
    ...
→ Ссылка
Автор решения: HolyBlackCat

Уф, поехали. Как обычно, я расчитываю что пользователи MSVC пользуются /std:c++latest /Zc:preprocessor (или 20 вместо latest) (работать со старым препроцессором - последнее дело).

Есть три варианта:

  1. Ровно как вы хотите, через макрос. Для этого нужно нагенерировать 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 - чтобы проносить дополнительную информацию в цикл снаружи цикла, если она там нужна (или можно оставить пустым).

    Этот вариант очень удачно игнорирует запятую после последнего аргумента.

  2. Через макрос, но с другим синтаксисом: 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 макросов, но непонятно зачем так делать, когда есть предыдущий вариант.

  3. Ну и третий способ - использовать шаблон вместо макроса, как предложили в соседнем ответе. Но там предложили не самый лучший способ это делать: во-первых рекурсия не нужна, а во-вторых (особенно при рекурсии!) аргументы лучше передавать по ссылке. (уже починили)

    В общем виде так:

    #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'), ...);
    }
    
→ Ссылка