Вывод типа аргумента шаблона из типа std:: function return с лямбдой

Сначала я использую С++ 11 (и моя тема отстой).

То, что я пытаюсь сделать, это написать общую функцию шаблона, которая реализует что-то обычно называемое sort_by на других языках программирования. Это предполагает вычисление произвольного критерия для каждого члена диапазона ровно один раз, а затем сортировку этого диапазона в соответствии с этими критериями. Такой критерий не должен быть POD, все, что он должен быть, менее чем сопоставимо. Для вещей, для которых std::less не работает, вызывающий должен быть в состоянии предоставить свой собственный функтор сравнения.

Я успешно написал указанную функцию, которая использует следующую подпись:

template<  typename Tcriterion
         , typename Titer
         , typename Tcompare = std::less<Tcriterion>
         >
void
sort_by(Titer first, Titer last,
        std::function<Tcriterion(typename std::iterator_traits<Titer>::value_type const &)> criterion_maker,
        Tcompare comparator = Tcompare()) {
}

Его можно использовать, например. например:

struct S { int a; std::string b; double c; };
std::vector<S> s_vec{
  { 42, "hello", 0.5 },
  { 42, "moo!",  1.2 },
  { 23, "fubar", 0.2 },
};

sort_by1< std::pair<int, double> >(
  s_vec.begin(), s_vec.end(),
  [](S const &one_s) { return std::make_pair(one_s.a, one_s.c); }
);

Что мне не нравится в этом подходе, так это то, что я должен сам предоставить аргумент Tcriterion, потому что компилятор не может вывести этот тип из выражения лямбда. Поэтому это не работает:

sort_by1(s_vec.begin(), s_vec.end(), [](S const &one_s) { return std::make_pair(one_s.a, one_s.c); });

clang 3.1 и gcc 4.7.1 оба коры на этом (gcc 4.7.1 даже лает на код выше, поэтому я думаю, я действительно делаю что-то неправильно здесь).

Однако, если я сначала назначу лямбда на std::function, тогда по крайней мере clang 3.1 может вывести аргумент, то есть это работает:

typedef std::pair<int, double> criterion_type;
std::function<criterion_type(S const &)> criterion_maker = [](S const &one_s) {
  return std::make_pair(one_s.a, one_s.c);
};
sort_by1(s_vec.begin(), s_vec.end(), criterion_maker);

Итак, мои вопросы: как мне изменить свою сигнатуру функции, чтобы мне не нужно было указывать один аргумент? И (возможно, связанный), как бы исправить мой пример, чтобы он работал с gcc?

Ответ 1

Не используйте std::function в тандеме с выводом аргумента шаблона. На самом деле, вероятно, нет причин использовать std::function в списке аргументов функции или функции. Чаще всего вам не следует использовать std::function; это очень специализированный инструмент, который очень хорош в решении одной конкретной проблемы. В остальное время вы можете обойтись без него.

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

struct less {
    template<typename T, typename U>
    auto operator()(T&& t, U&& u) const
    -> decltype( std::declval<T>() < std::declval<U>() )
    { return std::forward<T>(t) < std::forward<U>(u); }

    // operator< is not appropriate for pointers however
    // the Standard defines a 'composite pointer type' that
    // would be very helpful here, left as an exercise to implement
    template<typename T, typename U>
    bool operator()(T* t, U* u) const
    { return std::less<typename std::common_type<T*, U*>::type> {}(t, u); }
};

Затем вы можете объявить:

template<typename Iter, typename Criterion, typename Comparator = less>
void sort_by(Iter first, Iter last, Criterion crit, Comparator comp = less {});

и comp(*ita, *itb) будут поступать правильно, а также comp(crit(*ita), crit(*itb)) или что-либо еще, если это имеет смысл.

Ответ 2

Как насчет чего-то вроде этого:

template<  typename Titer
         , typename Tmaker
         , typename Tcompare
         >
void
sort_by(Titer first, Titer last,
        Tmaker criterion_maker,
        Tcompare comparator)
{
  typedef decltype(criterion_maker(*first)) Tcriterion;
  /*
    Now that you know the actual type of your criterion,
    you can do the real work here
  */
}

Проблема заключается в том, что вы, очевидно, не можете использовать по умолчанию для компаратора, но вы можете легко преодолеть это, предоставляя перегрузку, которая не принимает компаратора и заполняет внутри std::less внутренне.

Чтобы сделать это, как вы изначально предложили, компилятор должен был бы "инвертировать" процесс создания шаблона. То есть для данного экземпляра std:: function < > , какой параметр я должен предоставить в качестве результата для его получения. Это "выглядит" легко, но это не так!

Ответ 3

Вы также можете использовать что-то вроде этого.

     template<  typename Titer
     , typename Tmaker
     , typename TCriterion = typename
     std::result_of
     <
      Tmaker
      (
        decltype(*std::declval<Titer>()) 
      )
     >::type
     , typename Tcompare = std::less<TCriterion>
     >
void
sort_by(Titer first, Titer last,
        Tmaker criterion_maker, Tcompare comparator = Tcompare())
{
}

http://liveworkspace.org/code/0aacc8906ab4102ac62ef0e45a37707d