2013-06-27 2 views
32

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

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 функции.

ответ

49

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

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

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

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

«но ничего не хранит». Компилятор, скорее всего, выделит для этого столько же пространства, поскольку аргументы также нуждаются в пространстве (и, возможно, больше, для информации о вызовах и т. д.). И если вы спекулируете на том, чтобы получить оптимизацию, я думаю, что хранилище массивов точно так же будет оптимизировано. – Xeo

+9

Забавно, как более продвинутый C++ неизбежно приведет к более продвинутым хакам, чтобы обойти все, что невозможно в ясном и сжатом виде. Спасибо, что написали это! – rubenvb

+3

абсолютно блестящий ответ :) – Gabriel

7

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

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

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

+1

Не делает ли это копию независимо от того, что было передано? Разве это не могло быть смертельно опасным, если «печатать» огромный объект? – rubenvb

+1

Als, ваша ссылка идет на несвязанный фрагмент. – rubenvb

+2

Хотя это действительно выглядит Pretty ... довольно, вы, вероятно, должны написать что-то вроде [...] 'using value_type = std :: common_type_t ; for (auto const & arg: {static_cast (Args) ...}) '[...], чтобы позволить ему работать с гетерогенными пакетами параметров. Также @rubenvb Я считаю, что ваши проблемы касаются того, что std :: initializer_list <> был инициализирован с копией? – mbw

3

C++ 17 раз выражение:

(f(args), ...); 

Держите простых вещи простого ;-)

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

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