Подтвердить что ты не робот

Как вызвать функцию во всех вариационных шаблонных аргументах?

Я хотел бы сделать

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   print(Args)...;
}

И пусть это будет эквивалентно этой довольно объемной рекурсивной цепочке:

template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
  print(t);
  print(Args...);
}

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

"Проблема" с рекурсивной реализацией заключается в том, что создается много избыточного кода, поскольку каждый рекурсивный шаг приводит к новой функции аргументов N-1, тогда как код, который я хотел бы иметь, будет генерировать только код для единственная функция N -arg print и имеют не более N специализированные функции print.

4b9b3361

Ответ 1

Типичным подходом здесь является использование немого списка-инициализатора и расширение внутри него:

{ print(Args)... }

Порядок оценки гарантированно слева направо в фигурных инициаторах.

Но print возвращает void, поэтому нам нужно обойти это. Позвольте сделать его int тогда.

{ (print(Args), 0)... }

Это не будет работать как инструкция напрямую. Нам нужно указать тип.

using expand_type = int[];
expand_type{ (print(Args), 0)... };

Это работает до тех пор, пока в пакете Args всегда есть один элемент. Матрицы нулевого размера недействительны, но мы можем обойти это, сделав его всегда имеющим хотя бы один элемент.

expand_type{ 0, (print(Args), 0)... };

Мы можем сделать этот шаблон многоразовым с помощью макроса.

namespace so {
    using expand_type = int[];
}

#define SO_EXPAND_SIDE_EFFECTS(PATTERN) ::so::expand_type{ 0, ((PATTERN), 0)... }

// usage
SO_EXPAND_SIDE_EFFECTS(print(Args));

Однако для этого многократного использования требуется немного больше внимания к некоторым деталям. Мы не хотим использовать здесь перегруженные запятые. Запятая не может быть перегружена одним из аргументов void, поэтому давайте воспользоваться этим.

#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
        ::so::expand_type{ 0, ((PATTERN), void(), 0)... }

Если вы paranoid боитесь, что компилятор не назначает большие массивы нулей, вы можете использовать какой-то другой тип, который может быть инициализирован таким списком, но ничего не сохраняет.

namespace so {
    struct expand_type {
        template <typename... T>
        expand_type(T&&...) {}
    };
}

Ответ 2

С++ 17-кратное выражение:

(f(args), ...);

Сохраняйте простые вещи просто: -)

Если вы вызываете то, что может вернуть объект с перегруженным оператором запятой, используйте:

((void)f(args), ...);

Ответ 3

Вы можете использовать еще более простой и понятный подход

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   for (const auto& arg : {Args...})
   {
      print(arg);
   }
}

Я играл с обоими вариантами на компилировать проводник, и как gcc, так и clang с O3 или O2 производят точно такой же код, но мой вариант явно чище.