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

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

template void print(ArgTypes... Args) { print(Args)...; } 

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

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

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

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

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

 { 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)... } 

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

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

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

 (f(args), ...); 

Держите простые вещи простыми 😉

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

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

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

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

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

  • пакеты параметров шаблона доступа N-го типа и N-го элемента
  • Расширение шаблона шаблонов Variadic
  • Проверить свойства всех аргументов шаблона шаблона
  • make_unique и совершенная переадресация
  • C ++ 11 «перегруженная lambda» с вариационным шаблоном и захватом переменной
  • Есть ли название для этой именования создания кортежа?
  • Вызов функции для каждого вариационного аргумента шаблона и массива
  • Генерирование выражений парсера Spirit из вариационного списка альтернативных выражений парсера
  • Interesting Posts
    Давайте будем гением компьютера.