Reputation: 69
I want to mimic Ruby's map()
method in C++. I am struggling to figure out the return type automatically:
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
typedef std::string T2;
template<class T1,
// class T2, // gives "couldn't deduce template parameter 'T2'"
class UnaryPredicate>
std::vector<T2> map(std::vector<T1> in, UnaryPredicate pred)
{
std::vector<T2> res(in.size());
std::transform(in.begin(), in.end(), res.begin(), pred);
return res;
}
int main()
{
std::vector<int> v1({1,2,3});
auto v2(map(v1, [](auto el) { return "'"+std::to_string(el+1)+"'"; }));
std::cout << v2[0] << "," << v2[1] << "," << v2[2] << std::endl;
}
This way it compiles, but T2
is fixed to string
. If I use the other T2
definition the compiler complains couldn't deduce template parameter 'T2'
.
I also tried to make use of std::declval
, but probably not the right way - I was unable to solve the problem.
Upvotes: 5
Views: 1608
Reputation: 20579
Use decltype
+ std::decay_t
:
template <class T, class UnaryPredicate>
auto map(const std::vector<T>& in, UnaryPredicate pred)
{
using result_t = std::decay_t<decltype(pred(in[0]))>;
std::vector<result_t> res;
res.reserve(in.size());
std::transform(in.begin(), in.end(), std::back_inserter(res), pred);
return res;
}
Example usage:
std::vector v1{1, 2, 3};
auto v2 = map(v1, [](int el) { return "'" + std::to_string(el + 1) + "'"; });
std::cout << v2[0] << ", " << v2[1] << ", " << v2[2] << '\n';
Please also note the following changes that I made:
I changed in
to take by const reference instead of by value. This avoids unnecessary copies.
I used reserve
+ back_inserter
, instead of value initialization + assignment.
I used auto
as the return type. This enables return type deduction. The res
vector is guaranteed not to be copied. It is also eligible for copy elision.
You can list-initialize from a braced-init-list directly, so remove the parentheses surrounding the braced-init-list.
std::endl
should not be used when \n
is sufficient. std::endl
causes the buffer to be flushed, while \n
does not. Unnecessary flushing can cause performance degradation. See std::endl
vs \n
.
Upvotes: 12
Reputation: 20938
To simplify use auto
as return type, and value_type
of vector can be specified by declval
- call UnaryPredicate
for T1
:
template<class T1,
class UnaryPredicate>
auto map(std::vector<T1> in, UnaryPredicate pred)
{
std::vector< decltype(std::declval<UnaryPredicate>()(T1{})) > res(in.size());
std::transform(in.begin(), in.end(), res.begin(), pred);
return res;
}
Upvotes: 4