Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deduce the return type of a lambda?

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.

like image 489
wolfgang Avatar asked Nov 10 '19 10:11

wolfgang


People also ask

How do you specify the return type of lambda?

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.

Can Lambda have a return type?

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.

How do you return a value from a lambda function in C++?

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.


2 Answers

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:

  1. I changed in to take by const reference instead of by value. This avoids unnecessary copies.

  2. I used reserve + back_inserter, instead of value initialization + assignment.

  3. 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.

  4. You can list-initialize from a braced-init-list directly, so remove the parentheses surrounding the braced-init-list.

  5. 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.

like image 70
L. F. Avatar answered Oct 05 '22 07:10

L. F.


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

like image 37
rafix07 Avatar answered Oct 05 '22 08:10

rafix07