I'm trying to write a projection function that could transform a vector<T>
into a vector<R>
. Here is an example:
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"}
First attempt:
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;
}
But for
auto r1 = select(v, [](int e){return e*e; });
I get:
error C2660: 'select' : function does not take 2 arguments
I have to explicitly call select<int,int>
to work. I don't like this because the types are redundant.
auto r1 = select<int, int>(v, [](int e){return e*e; }); // OK
Second attempt:
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 result is same error, function does not take 2 arguments. In this case I actually have to supply a 3rd type argument:
auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });
Third attempt:
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;
}
For
auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });
the error is:
'select' : invalid template argument for 'Selector', class template expected
For
auto r1 = select(v, [](int e){return e*e; });
error C2660: 'select' : function does not take 2 arguments
(I know the last two attempts are not particularly great.)
How can I write this select()
template function to work for the sample code I put in the beginning?
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