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.
The return type for a lambda is specified using a C++ feature named 'trailing return type'. This specification is optional. Without the trailing return type, the return type of the underlying function is effectively 'auto', and it is deduced from the type of the expressions in the body's return statements.
The return type of a lambda expression is automatically deduced. You don't have to use the auto keyword unless you specify a trailing-return-type. The trailing-return-type resembles the return-type part of an ordinary function or member function.
Generally return-type in lambda expression are evaluated by compiler itself and we don't need to specify that explicitly and -> return-type part can be ignored but in some complex case as in conditional statement, compiler can't make out the return type and we need to specify that.
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';
(live demo)
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
.
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;
}
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With