Во-первых, шаблоны с переменным числом аргументов не включают способ сказать «переменное число аргументов одного типа».При использовании шаблонов с переменными значениями вы получаете пакет параметров, представляющий собой набор из нуля или более аргументов, каждый из которых может иметь уникальный тип:
template<typename... Ts> void foo(Ts... ts);
маркер ...
имеет только определенные значения для этих пакетов параметров (и функции vararg, но это не относится к делу).Таким образом, вы не можете использовать его с пакетами без параметров:
template<typename T> void foo(T... t); // error
template<typename T> void foo(T t...); // error
Во-вторых, когда у вас есть пакет параметров, вы не можете просто перебирать параметры так, как вы показываете их с помощью range-на основе цикла.Вместо этого вы должны написать свои алгоритмы в функциональном стиле, используя расширение пакета параметров, чтобы «отделить» параметры от пакета параметров.
// single argument base case
template<typename T>
void foo(T t) {
std::cout << t;
}
template<typename T,typename... Us>
void foo(T t,Us... us) {
foo(t) // handle first argument using single argument base case, foo(T t)
foo(us...); // 'recurse' with one less argument, until the parameter pack
// only has one argument, then overload resolution will select foo(T t)
}
Хотя шаблоны с переменными параметрами напрямую не поддерживают то, что вы хотите, вы можетеиспользуйте enable_if
и используйте правило 'SFINAE', чтобы наложить это ограничение.Сначала вот версия, которая без ограничения:
#include <type_traits>
#include <utility>
template<class T>
T min(T t) {
return t;
}
template<class T,class... Us>
typename std::common_type<T,Us...>::type
min(T t,Us... us)
{
auto lowest = min(us...);
return t<lowest ? t : lowest;
}
int main() {
min(1,2,3);
}
А затем примените enable_if
, чтобы убедиться, что все типы одинаковы.
template<class T,class... Us>
typename std::enable_if<
std::is_same<T,typename std::common_type<Us...>::type>::value,
T>::type
min(T t,Us... us)
{
auto lowest = min(us...);
return t<lowest ? t : lowest;
}
Модифицированная реализация выше предотвратитФункция используется в любое время, когда аргументы не совпадают в соответствии с is_same
.
Возможно, вам лучше не использовать эти приемы, если вам не нужно.Использование initializer_list, как подсказывает KennyTM, вероятно, является лучшей идеей.Фактически, если вы действительно реализуете min и max, вы можете избавить себя от хлопот, потому что стандартная библиотека уже содержит перегрузки, которые принимают initializer_list.
Как работает is_same<T,typename common_type<Us...>::type>
?
Поскольку существует версия с одним аргументом min()
, версия с переменным числом выбирается только при наличии двух или более параметров.Это означает, что sizeof...(Us)
является хотя бы одним.В случае, если он ровно один, common_type<Us...>
возвращает этот тип одиночного типа, а is_same<T,common_type<Us...>>
гарантирует, что оба типа совпадают.
Вариативная реализация min()
вызывает min(us...)
.Пока этот вызов работает только тогда, когда все типы в Us...
одинаковы, мы знаем, что commont_type<Us...>
сообщает, что это за тип, а is_same<T,common_type<Us...>>
гарантирует, что T также тот же тип.
Итак, мы знаем, что min(a,b)
работает, только если a
и b
одного типа.И мы знаем, что min(c,a,b)
вызывает min(a,b)
, поэтому min(c,a,b)
можно вызывать только в том случае, если a
и b
относятся к одному типу, и дополнительно, если c
также того же типа.min(d,c,a,b)
вызывает min(c,a,b)
, поэтому мы знаем, что min(d,c,a,b)
можно вызывать, только если c
, a
и b
имеют одинаковый тип, и дополнительно, если d
также является тем же типом.И т.д.