11

I have the following template.

template<typename T, typename U>
std::vector<U> map(const std::vector<T> &v, std::function<U(const T&)> f) {
    std::vector<U> res;
    res.reserve(v.size());
    std::transform(std::begin(v), std::end(v), std::end(res), f);
    return res;
}

When I use it in my code I have the specify the template parameters. Why is the compiler not able to deduce this for me? How do I have to change my template definition to make this work?

vector<int> numbers = { 1, 3, 5 };

// vector<string> strings = map(numbers, [] (int x) { return string(x,'X'); });

vector<string> strings = map<int, string>(numbers, [] (int x) { return string(x,'X'); });

Runnable code: http://ideone.com/FjGnxd

The original code in this question comes from here: The std::transform-like function that returns transformed container

Community
  • 1
  • 1
Julian Lettner
  • 3,309
  • 7
  • 32
  • 49

1 Answers1

21

Your function expects an std::function argument, but you're calling it with a lambda expression instead. The two are not the same type. A lambda is convertible to std::function, but template argument deduction requires exact matches and user defined conversions are not considered. Hence the deduction failure.

Deduction does work if you actually pass an std::function to map().

std::function<string(int const&)> fn = [] (int x) { return string(x,'X'); };
vector<string> strings = map(numbers, fn);

Live demo


One possible workaround to avoid having to specify the template arguments is to modify the function to accept any kind of callable, rather than an std::function object.

template<typename T, typename Func>
std::vector<typename std::result_of<Func(T)>::type>
    map(const std::vector<T> &v, Func f) {
        // ...
    }

Another version of the same idea, using decltype and declval instead of result_of

template<typename T, typename Func>
std::vector<decltype(std::declval<Func>()(std::declval<T>()))>
    map(const std::vector<T> &v, Func f) {
        // ...
    }

Finally, using a trailing return type

template<typename T, typename Func>
auto map(const std::vector<T> &v, Func f) 
  -> std::vector<decltype(f(v[0]))> {
        // ...
    }

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Usually, `typename std::result_of::type` shows up in multiple places inside the `map` function. You cannot typedef it to make it shorter, but is their a way to create an alias for it so there is no repetition? – Ferenc Jan 27 '17 at 17:30
  • 1
    @Ferenc If you have C++14, it can be shortened to `std::result_of_t`. Or you can write your own `template using result_of_t = typename result_of::type;` – Praetorian Jan 27 '17 at 18:26