Я пытаюсь написать функцию проекции, которая может преобразовать 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()
функция шаблона для работы с примером кода, который я положил в начале?
основной 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;
}
основной 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;
}
продвинутый 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;
}
продвинутый 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
Ваша первая проблема заключается в том, что вы думаете, что лямбда-это 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
который берет диапазон и записывает его в вектор, если вам нужно избавиться от ленивых вычислений. Но для этого нужно писать целую библиотеку, а не решать вашу проблему.