Как вывести тип возврата объекта функции из списка параметров?

Я пытаюсь написать функцию проекции, которая может преобразовать vector<T> в vector<R>, Вот пример:

auto v = std::vector<int> {1, 2, 3, 4};
auto r1 = select(v, [](int e){return e*e; }); // {1, 4, 9, 16}
auto r2 = select(v, [](int e){return std::to_string(e); }); // {"1", "2", "3", "4"}

Первая попытка:

template<typename T, typename R>
std::vector<R> select(std::vector<T> const & c, std::function<R(T)> s)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}

Но для

auto r1 = select(v, [](int e){return e*e; });

Я получил:

ошибка C2660: «выбор»: функция не принимает 2 аргумента

Я должен явно позвонить select<int,int> работать. Мне это не нравится, потому что типы избыточны.

auto r1 = select<int, int>(v, [](int e){return e*e; }); // OK

Вторая попытка:

template<typename T, typename R, typename Selector>
std::vector<R> select(std::vector<T> const & c, Selector s)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}

В результате та же ошибка, функция не принимает 2 аргумента. В этом случае я фактически должен предоставить аргумент третьего типа:

auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });

Третья попытка:

template<typename T, typename R, template<typename, typename> class Selector>
std::vector<R> select(std::vector<T> const & c, Selector<T,R> s)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}

За

auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });

ошибка:

‘select’: неверный аргумент шаблона для ‘Selector’, ожидается шаблон класса

За

auto r1 = select(v, [](int e){return e*e; });

ошибка C2660: «выбор»: функция не принимает 2 аргумента

(Я знаю, что последние две попытки не особенно хороши.)

Как я могу написать это select() функция шаблона для работы с примером кода, который я положил в начале?

18

Решение

Опция 1:

основной decltype() использование:

template <typename T, typename F>
auto select(const std::vector<T>& c, F f)
-> std::vector<decltype(f(c[0]))>
{
using R = decltype(f(c[0]));
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
return v;
}

Вариант № 2:

основной std::result_of<T> использование:

template <typename T, typename F, typename R = typename std::result_of<F&(T)>::type>
std::vector<R> select(const std::vector<T>& c, F f)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
return v;
}

Вариант № 3:

продвинутый decltype() использование и совершенной экспедиторская (см. примечания *):

template <typename T, typename A, typename F>
auto select(const std::vector<T, A>& c, F&& f)
-> std::vector<typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type>
{
using R = typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type;
std::vector<R> v;
std::transform(std::begin(c), std::end(c)
, std::back_inserter(v)
, std::forward<F>(f));
return v;
}

Вариант № 4:

продвинутый std::result_of<T> использование и совершенной экспедиторская (см. примечания *):

template <typename T, typename A, typename F, typename R = typename std::decay<typename std::result_of<typename std::decay<F>::type&(typename std::vector<T, A>::const_reference)>::type>::type>
std::vector<R> select(const std::vector<T, A>& c, F&& f)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c)
, std::back_inserter(v)
, std::forward<F>(f));
return v;
}

* Заметка: Варианты № 3 и № 4 предполагают, что std::transform алгоритм принимает функциональный объект по значению, а затем использует его как неконстантное значение. Вот почему можно увидеть это странное typename std::decay<F>::type& синтаксис. Если объект функции должен быть вызван в select сама функция, и тип результата не будет использоваться в качестве аргумента шаблона контейнера (с целью внешний наиболее std::decay<T> используется), то правильный и переносимый синтаксис для получения возвращаемого типа:

/*#3*/ using R = decltype(std::forward<F>(f)(*c.begin()));

/*#4*/ typename R = typename std::result_of<F&&(typename std::vector<T, A>::const_reference)>::type
23

Другие решения

Ваша первая проблема заключается в том, что вы думаете, что лямбда-это std::function, std::function и лямбда не связаны между собой. std::function<R(A...)> является объектом стирания типа, который может преобразовывать все, что (A) копируемое, (B) разрушаемое и (C) может быть вызвано с помощью A... и возвращает тип, совместимый с Rи стирает всю другую информацию о типе.

Это означает, что он может потреблять совершенно не связанные типы, если они проходят эти тесты.

Лямбда — это анонимный класс, который может быть уничтожен, может быть скопирован (за исключением C ++ 14, где это иногда бывает) и имеет operator() который вы укажете. Это означает, что вы часто можете конвертировать лямбда в std::function с совместимой подписью.

Выводить std::function из лямбды не очень хорошая идея (есть способы сделать это, но они плохие идеи: C ++ 14 auto лямбды разбивают их, плюс вы получаете ненужную неэффективность.)

Так как мы решим вашу проблему? Как я понимаю, ваша проблема заключается в том, чтобы взять функциональный объект и контейнер и определить, какой элемент transform будет производиться после применения объекта функции к каждому элементу, так что вы можете сохранить результат в std::vector,

Это ответ, наиболее близкий к решению вашей проблемы:

template<typename T, typename R, typename Selector>
std::vector<R> select(std::vector<T> const & c, Selector s) {
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}

Самое простое, что нужно сделать, это поменять местами T а также R в шаблонном порядке, и пусть вызывающий абонент R явно, как select<double>, Это оставляет T а также Selector выводится. Это не идеально, но делает небольшое улучшение.

Для полного решения есть два способа исправить это решение. Во-первых, мы можем изменить select вернуть временный объект с operator std::vector<R>задерживая трансформацию до этой точки. Вот неполный набросок:

template<typename T, typename Selector>
struct select_result {
std::vector<T> const& c;
Selector s;
select_result(select_result&&)=default;
select_result(std::vector<T> const & c_, Selector&& s_):
c(c_), s(std::forward<Selector>(s_)
{}
operator std::vector<R>()&& {
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
};
template<typename T, typename Selector>
select_result<T, Selector> select(std::vector<T> const & c, Selector&& s) {
return {c, std::forward<Selector>(s)};
}

Я также могу предоставить гладкая версия что, к сожалению, зависит от неопределенного поведения (при захвате ссылок на локальные ссылки в функции возникают проблемы со временем жизни в соответствии со стандартом).

Но это избавляет от auto v = select синтаксис — в итоге вы сохраняете то, что дает результаты, а не результаты.

Вы все еще можете сделать std::vector<double> r = select( in_vec, [](int x){return x*1.5;} ); и это работает довольно хорошо.

По сути, я разделил удержание на две фазы: одну для аргументов и одну для возвращаемого значения.

Однако нет необходимости полагаться на это решение, так как есть и другие более прямые пути.

Для второго подхода мы можем вывести R сами:

template<typename T, typename Selector>
std::vector<typename std::result_of<Selector(T)>::type>
select(std::vector<T> const & c, Selector s) {
using R = typename std::result_of<Selector(T)>::type;
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}

что является довольно твердым решением. Прикосновение очистки:

// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&;
template<
typename T, typename A,
typename Selector,
typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(std::vector<T, A> const & c, Selector&& s) {
std::vector<R> v;
std::transform(begin(c), end(c), back_inserter(v), std::forward<Selector>(s));
return v;
}

делает это исправным решением. (прокачивает R в template списки типов, допустимые альтернативные распределители vectorудалил лишнее std::и сделал идеальную пересылку на Selector).

Тем не менее, мы можем сделать лучше.

Тот факт, что вход является vector довольно бессмысленно

template<
typename Range,
typename Selector,
typename R=typename std::result_of<Selector(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
std::vector<R> v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
return v;
}

который не компилируется из-за невозможности определить T пока что Итак, давайте работать над этим:

namespace details {
namespace adl_aux {
// a namespace where we can do argument dependent lookup on begin and end
using std::begin; using std::end;
// no implementation, just used to help with ADL based decltypes:
template<class R>
decltype( begin( std::declval<R>() ) ) adl_begin(R&&);
template<class R>
decltype( end( std::declval<R>() ) ) adl_end(R&&);
}
// pull them into the details namespace:
using adl_aux::adl_begin;
using adl_aux::adl_end;
}
// two aliases.  The first takes a Range or Container, and gives
// you the iterator type:
template<class Range>
using iterator = decltype( details::adl_begin( std::declval<Range&>() ) );
// the second is syntactic sugar on top of `std::iterator_traits`:
template<class Iterator>
using value_type = typename std::iterator_traits<Iterator>::value_type;

что дает нам iterator<Range> а также value_type<Iterator> псевдонимы. Вместе они позволяют нам сделать вывод T без труда:

// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&;

template<
typename Range,
typename Selector,
typename T=value_type<iterator<Range&>>,
typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
std::vector<R> v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
return v;
}

а также боб твой дядя. (decayed_lvalue отражает то, как Selector тип используется для угловых случаев, и iterator<Range&> отражает то, что мы получаем итератор из версии lvalue Range).

В VS2013 иногда выше decltypeпутают половинную реализацию C ++ 11, которую они имеют. Замена iterator<Range> с decltype(details::adl_begin(std::declval<Range>())) настолько безобразно, насколько это может решить эту проблему.

// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&;

template<
typename Range,
typename Selector,
typename T=value_type<decltype(details::adl_begin(std::declval<Range&>()))>,
typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
std::vector<R> v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
return v;
}

Результирующая функция будет принимать массивы, векторы, списки, карты или пользовательские записанные контейнеры, и будет принимать любую функцию преобразования и генерировать вектор результирующего типа.

Следующий шаг — сделать преобразование ленивым, а не помещать его прямо в vector, Вы можете иметь as_vector который берет диапазон и записывает его в вектор, если вам нужно избавиться от ленивых вычислений. Но для этого нужно писать целую библиотеку, а не решать вашу проблему.

11

По вопросам рекламы [email protected]