Как вызвать функцию во всех вариационных шаблонных аргументах?
Я хотел бы сделать
template void print(ArgTypes... Args) { print(Args)...; }
И пусть это будет эквивалентно этой довольно объемной рекурсивной цепочке:
template void print(const T& t, ArgTypes... Args) { print(t); print(Args...); }
за которыми следуют явные однопараметрические специализации для каждого типа, который я хотел бы напечатать.
- Что делает этот код вариационного шаблона?
- В чем смысл «... ...» токена? т.е. двойной многоточий оператор на пачке параметров
- Как получить типы аргументов указателя функции в classе вариационных шаблонов?
- «Распаковка» кортежа для вызова соответствующего указателя функции
- Каковы правила для токена «...» в контексте вариативных шаблонов?
«Проблема» с рекурсивной реализацией заключается в том, что генерируется много избыточного кода, потому что каждый рекурсивный шаг приводит к новой функции аргументов N-1
, тогда как код, который я хотел бы иметь, будет генерировать код только для одного N
-arg print
и иметь не более N
специализированных функций print
.
- C ++ 11: я могу перейти от нескольких аргументов к кортежу, но могу ли я перейти от кортежа к нескольким аргументам?
- Как хранить вариационные аргументы шаблона?
- Ограничить переменные аргументы шаблона
- Возможно ли «сохранить» пакет параметров шаблона без его расширения?
- проверить параметры вариационных шаблонов для уникальности
- рекурсивный вариационный шаблон для распечатки содержимого пакета параметров
- Шаблон функции Variadic с расширением пакета не в последнем параметре
- Как создать декартово произведение списка типов?
Типичным подходом здесь является использование немого списка-инициализатора и расширение внутри него:
{ 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 производят точно такой же код, но мой вариант явно чище.