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
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
std::vector select(std::vector const & c, Selector s) {
std::vector 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
. 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
, delaying the transformation to that point. Here is an incomplete sketch:
template
struct select_result {
std::vector const& c;
Selector s;
select_result(select_result&&)=default;
select_result(std::vector const & c_, Selector&& s_):
c(c_), s(std::forward(s_)
{}
operator std::vector()&& {
std::vector v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
};
template
select_result select(std::vector const & c, Selector&& s) {
return {c, std::forward(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
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
std::vector::type>
select(std::vector const & c, Selector s) {
using R = typename std::result_of::type;
std::vector 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
using decayed_lvalue = typename std::decay::type&;
template<
typename T, typename A,
typename Selector,
typename R=typename std::result_of(T)>::type
>
std::vector select(std::vector const & c, Selector&& s) {
std::vector v;
std::transform(begin(c), end(c), back_inserter(v), std::forward(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::type
>
std::vector select(Range&& in, Selector&& s) {
std::vector v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward(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
decltype( begin( std::declval() ) ) adl_begin(R&&);
template
decltype( end( std::declval() ) ) 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
using iterator = decltype( details::adl_begin( std::declval() ) );
// the second is syntactic sugar on top of `std::iterator_traits`:
template
using value_type = typename std::iterator_traits::value_type;
which gives us an iterator
and value_type
aliases. Together they let us deduce T
easily:
// std::transform takes by-value, then uses an lvalue:
template
using decayed_lvalue = typename std::decay::type&;
template<
typename Range,
typename Selector,
typename T=value_type>,
typename R=typename std::result_of(T)>::type
>
std::vector select(Range&& in, Selector&& s) {
std::vector v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward(s));
return v;
}
and bob is your uncle. (decayed_lvalue
reflects how the Selector
type is used for corner cases, and iterator
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
with decltype(details::adl_begin(std::declval
as ugly as that is can fix that issue.
// std::transform takes by-value, then uses an lvalue:
template
using decayed_lvalue = typename std::decay::type&;
template<
typename Range,
typename Selector,
typename T=value_type()))>,
typename R=typename std::result_of(T)>::type
>
std::vector select(Range&& in, Selector&& s) {
std::vector v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward(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.