I\'m trying to write a projection function that could transform a vector
into a vector
. Here is an example:
auto v =
Your first problem is that you think that a lambda is a std::function
. A std::function
and a lambda are unrelated types. std::function<R(A...)>
is a type erasure object that can convert anything that is (A) copyable, (B) destroyable and (C) can be invoked using A...
and returns a type compatible with R
, and erases all other information about the type.
This means it can consume completely unrelated types, so long as they pass those tests.
A lambda is an anonymous class which is destroyable, can be copied (except in C++14, where this is sometimes), and has an operator()
which you specify. This means you can often convert a lambda into a std::function
with a compatible signature.
Deducing the std::function
from the lambda is not a good idea (there are ways to do it, but they are bad ideas: C++14 auto
lambdas break them, plus you get needless inefficiency.)
So how do we solve your problem? As I see it, your problem is taking a function object and a container and deducing what kind of element transform
would produce after applying the function object on each element, so you can store the result in a std::vector
.
This is the answer that is closest to the solution to your problem:
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;
}
The easiest thing to do is swap T
and R
in template order, and have the caller pass in R
explicitly, like select<double>
. This leaves T
and Selector
being deduced. This isn't ideal, but it does do a small improvement.
For a full solution, there are two ways to approach fixing this solution. First, we can change select
to return a temporary object with an operator std::vector<R>
, delaying the transformation to that point. Here is an incomplete sketch:
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)};
}
I can also provide a slicker version that sadly relies on undefined behavior (reference capture of local references in a function have lifetime issues under the standard).
But that gets rid of auto v = select
syntax -- you end up storing the thing that produces results, rather than the results.
You can still do std::vector<double> r = select( in_vec, [](int x){return x*1.5;} );
and it works pretty well.
Basically I have split deduction into two phases, one for arguments, and one for return value.
However, there is little need to rely on that solution, as there are other more direct ways.
For a second approach, we can deduce R
ourselves:
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;
}
which is a pretty solid solution. A touch of cleanup:
// 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;
}
makes this a serviceable solution. (moved R
to template
type lists, allowed alternative allocators to the vector
, removed some needless std::
, and did perfect forwarding on the Selector
).
However, we can do better.
The fact that the input is a vector
is pretty pointless:
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;
}
which doesn't compile due to the inability to determine T
as yet. So lets work on that:
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;
which gives us an iterator<Range>
and value_type<Iterator>
aliases. Together they let us deduce T
easily:
// 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;
}
and bob is your uncle. (decayed_lvalue
reflects how the Selector
type is used for corner cases, and iterator<Range&>
reflects that we are getting an iterator from the lvalue version of Range
).
In VS2013 sometimes the above decltype
s confuse the half-implementation of C++11 they have. Replacing iterator<Range>
with decltype(details::adl_begin(std::declval<Range>()))
as ugly as that is can fix that issue.
// 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;
}
The resulting function will take arrays, vectors, lists, maps, or custom written containers, and will take any transformation function, and produce a vector of the resulting type.
The next step is to make the transformation lazy instead of putting it directly into a vector
. You can have as_vector
which takes a range and writes it out to a vector if you need to get rid of lazy evaluation. But that is getting into writing an entire library rather than solving your problem.
Basic decltype()
usage:
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;
}
Basic std::result_of<T>
usage:
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;
}
Advanced decltype()
usage and perfect-forwarding (see notes*):
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;
}
Advanced std::result_of<T>
usage and perfect-forwarding (see notes*):
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;
}
* Note: Options #3 and #4 assume that the std::transform
algorithm takes a function object by-value, and then uses it as a non-const lvalue. This is why one can see this strange typename std::decay<F>::type&
syntax. If the function object is supposed to be called within the select
function itself, and the result type is not going to be used as a container's template argument (for the purpose of what the outer-most std::decay<T>
is used), then the correct and portable syntax for obtaining the return type is:
/*#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