1

I'd like to pass a lambda without using a function template here, but with std::function or a typed function pointer.

#include <iostream>
#include <list>
#include <functional>

template<typename T>
std::list<T> mapll(std::function<T(T)> f, std::list<T> l) {
    for(auto it = l.begin(); it != l.end(); ++it) {
        *it = f(*it);
    }
}
int main() {
    // Make it
    std::list<int> l = std::list<int> {1,2,3};
    // Map it
    mapll([](int n) { return n * 2; }, l);
    // Print it
    std::cout << "{";
    for (auto it = l.begin(); it != l.end(); ++it) {
        if (it == l.begin())
            std::cout << "," << *it;
        std::cout << *it;
    }
} 

But I'm not succeeding in calling mapll. The error is:

clang++ -g -DNDEBUG -std=c++17 -Wno-switch-bool -lc main.cpp -o main

main.cpp:51:2: error: no matching function for call to 'mapll'
        mapll([](int n) { return n * 2; }, l);

^~~~~ main.cpp:42:14: note: candidate template ignored: could not match 
'function<type-parameter-0-0 (type-parameter-0-0)>' against
    '(lambda at main.cpp:51:8)' std::list<T> mapll(std::function<T(T)> f, std::list<T> l) {
JeJo
  • 30,635
  • 6
  • 49
  • 88
rausted
  • 951
  • 5
  • 21
  • 2
    I recommend you look at how the standard library handles predicates and passed functions. For example take a look at [`std::for_each`](http://en.cppreference.com/w/cpp/algorithm/for_each). That should also show you the preferred way of handling "containers" being passed to functions, by iterator ranges instead of as containers. – Some programmer dude Aug 22 '18 at 06:50
  • 2
    By the way, the more generic variant of what you're trying to do is the four-argument overload of [`std::transform`](https://en.cppreference.com/w/cpp/algorithm/transform). You could do `std::transform(l.begin(), l.end(), l.begin(), [](int n) { return n * 2; });` instead of making up your own function. – Some programmer dude Aug 22 '18 at 06:53
  • Template instantiation does not try to perform implicit conversions in order to find a matching template. The types must match. – molbdnilo Aug 22 '18 at 06:53
  • Thanks for the comments, but I’m looking for a well-typed way to do this, not by making the function argument Generic. – rausted Aug 22 '18 at 07:00
  • 1
    @rausted lambdas don't have a known type, they aren't functions. Each lambda is _some_ class that can be called in predefined way. Capture-less lambdas can be cast to a function pointer. You can't be too strict here. – Swift - Friday Pie Aug 22 '18 at 07:04
  • @Swift-FridayPie I see, so if we printed the decltype of an inferred Generic function type in the capture-less case, it would show a function pointer, as in the answers below. If it was a capturing lambda, then it would show something else. – rausted Aug 22 '18 at 07:11

2 Answers2

2

The std::function argument is not deduced, because lamdbas are distinct types convertible to a std::function object, but this conversion is not part of type deduction. Here is how it should work with std::function:

template<typename T>
void mapll(std::function<T(T)> f, std::list<T>& l) {
    for(auto it = l.begin(); it != l.end(); ++it) {
        *it = f(*it);
    }
}

/* ... */

mapll(std::function<int(int)>{[](int n) { return n * 2; }}, l);

And as you mentioned a possible solution with function pointers, this would be it:

template<typename T>
void mapll(int (*f)(int),  std::list<T>& l) {
    /* Function body identical to the one above. */
}

/* ... */

/* The stateless lambda converts to the function pointer: */
mapll([](int n) { return n * 2; }, l);

Unfortunately, the above function pointer operates on ints, which doesn't make sense in a function template for std::list<T>. With a generic function pointer, the mapll function signature changes to

template<typename T>
void mapll(T (*f)(T),  std::list<T>& l)

but again, the lamdba for int argument/return type isn't deduced to match this function pointer. You'd have to explicitly cast it:

mapll(static_cast<int(*)(int)>([](int n) { return n * 2; }), l);

Note that as pointed out in the comments, you should also consider std::transform. And I have changed return value (to void) and second argument type (to an lvalue reference) of the mapll function template.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • Yes! I always forget the syntax for function pointers, thank you. Why did you give it specialized int arguments though? Is “mapll(T (*f)(T), ...” not possible? – rausted Aug 22 '18 at 07:05
  • Right, it definitely makes sense to have a generic function pointer, too. Unfortunately, this only works when explicitly casting the lambda. I'll add that to the answer. – lubgr Aug 22 '18 at 07:09
  • 1
    @rausted implicit casting won't work, link to reason was givenin JeJo's answer – Swift - Friday Pie Aug 22 '18 at 07:12
  • 2
    That’s really unfortunate, it seems I can either match on the exact function type “int (*f)(int)” or go completely typeless with a Func template. I can’t match on the T-> T shape of the function (ie: unary here) without sfinae tricks – rausted Aug 22 '18 at 07:19
  • @rausted that saves you and compiler from the more unfortunate case: dealing with platforms which have several call conventions that can be mixed. E.g... Windows. Lambda-pointer play there may be come tricky because there would be ambiguous conversions. – Swift - Friday Pie Aug 22 '18 at 07:22
1

The reason for the error has explained well here: C++11 does not deduce type when std::function or lambda functions are involved

If you want a generic solution, you need to define the lambda type by function pointer type:

typedef int(*funPtrType)(int); // define function pointer type

template<typename T>
void mapll(const funPtrType f, std::list<T>& l) {
   // code here
}

and then call it like:

mapll([](int n) { return n * 2; }, l);

The above solution can only be used for int type. If you want to work with generic data types, you need to use this:

template<typename T>
void mapll(T (*f)(T),  std::list<T>& l) {
   //  code here
}

Then you can either:

  1. explicitly cast the lambda to the function pointer type:

    mapll(static_cast<int(*)(int)>([](int n) { return n * 2; }), l);
          ^^^^^^^^^^^^^^^^^^^^^^^^
    

    or

  2. specify or qualify explicitly the type of the template parameter T like:

    mapll<int>([](Type n) { return n * 2; }, l);
         ^^^^^
    

However, the simplest solution is to add one more template para for function pointer and let the compiler decide the type.

template<typename T, typename FunPtr> // template para for function pointer
void mapll(FunPtr f, std::list<T>& l) {
    // code here
}

Note: You are not returning the list from the function. Therefore make it a void function and pass the list by ref as shown.

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Thank you but I’m not looking for a generic, I’m looking for a stricter type for the function argument here. – rausted Aug 22 '18 at 07:01