C ++ 11: Аргументы функции шаблона Variadic-однородного не POD? - PullRequest
4 голосов
/ 19 марта 2012

Как бы вы написали шаблонную функцию, которая принимает переменное число однородных аргументов не POD-функции в C ++ 11?

Например, предположим, что мы хотели написать функцию min для любого типа, который определяетменьше, чем «operator <» следующим образом: </p>

// pseduo-code...

template<class T...>
T min(T x1, T x2, ..., T xn)
{
    T lowest = x1;

    for (T x : {x2,...,xn})
       if (x < lowest)
           lowest = x;

    return lowest;
}

Вышеприведенный код является недопустимым C ++ 11, как бы вы написали его на законных основаниях?

Ответы [ 4 ]

10 голосов
/ 19 марта 2012

Однородная? Просто используйте std::initializer_list.

template <typename T>
T min_impl(std::initializer_list<T> values)
{
    return *std::min_element(values.begin(), values.end());
}

...

return min_impl({8, 5, 4, 1, 6});

(Как отмечалось @ Jesse , это эквивалентно std :: min в стандартной библиотеке.)

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

template <typename... T>
auto min(T&&... args) -> decltype(min_impl({std::forward<T>(args)...}))
{
    return min_impl({std::forward<T>(args)...});
}

...

return min(8, 5, 1, 4, 6);
3 голосов
/ 19 марта 2012

Во-первых, шаблоны с переменным числом аргументов не включают способ сказать «переменное число аргументов одного типа».При использовании шаблонов с переменными значениями вы получаете пакет параметров, представляющий собой набор из нуля или более аргументов, каждый из которых может иметь уникальный тип:

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 также является тем же типом.И т.д.

1 голос
/ 19 марта 2012

Это немного запутанно, но это то, что вы получите, если вам нужны разнородные аргументы:

#include <iostream>
#include <type_traits>

template<typename F, typename T, typename Arg>
auto fold(F f, T&& t, Arg&& a) 
  -> decltype(f(std::forward<T>(t), std::forward<Arg>(a)))
{ return f(std::forward<T>(t), std::forward<Arg>(a)); }

template<typename F, typename T, typename Head, typename... Args>
auto fold(F f, T&& init, Head&& h, Args&&... args) 
  -> decltype(f(std::forward<T>(init), std::forward<Head>(h)))
{ 
  return fold(f, f(std::forward<T>(init), std::forward<Head>(h)), 
              std::forward<Args>(args)...); 
}

// polymorphic less
struct p_less {
  template<typename T, typename U>
  typename std::common_type<T, U>::type 
  operator()(T&& t, U&& u) const {
    return t < u ? t : u;
  }
};

// heterogeneous arguments possible
template<typename Head, typename... Args>
auto min(Head&& h, Args&&... args) -> typename std::common_type<Head, Args...>::type
{
  return fold(p_less(), std::forward<Head>(h), 
              std::forward<Args>(args)...);
}


// only considers homogeneous arguments
template<typename Head, typename... Args>
auto hmin(Head&& h, Args&&... args) -> Head
{
  return fold([](Head x, Head y) -> Head { return x < y ? x : y; }, 
              std::forward<Head>(h), std::forward<Args>(args)...);
}

int main()
{

  double x = 2.0, x2 = 3.0;
  int y = 2;

  auto d1 = min(3, 4.0, 2.f, 6UL);
  auto d2 = min(x, y);
  auto d3 = hmin(x, x2);
  auto b = hmin(3, 2, 7, 10);

  std::cout << d1 << std::endl;
  std::cout << d2 << std::endl;
  std::cout << d3 << std::endl;

  std::cout << b << std::endl;
  return 0;
}

Однако первая версия намного интереснее.common_type ищет тип, к которому можно привести все аргументы.Это необходимо, потому что функция с разнородными аргументами, возвращающими любой из них, должна найти этот тип.Тем не менее, это можно обойти, используя boost::variant, но для встроенных типов это довольно бессмысленно, так как в любом случае их необходимо принудительно сравнивать.

0 голосов
/ 19 марта 2012

Другая возможность:

template <typename T, typename... T2>
T min(T x1, T2... rest);

template <typename T>
T min(T x)
{
  return x;
}

template <typename T, typename... T2>
T min(T x1, T2... rest)
{
  return std::min (x1, min (rest...));
}

Хотя это, по-видимому, не обеспечивает однородности, звоните, например, с помощью int и long выдают ошибки компиляции. К сожалению, я не настолько хорош в стандарте, чтобы говорить, если это так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...