Search code examples
c++c++11lambdatype-conversioncallable-object

No matching function error when passing lambda function as argument


I have a list of numbers.

I am trying to filter the list and only keep the positive numbers.

I am trying to do it by passing a lambda as an argument.

I wonder why I get function mismatch error.

#include <vector>
#include <algorithm>
#include <functional>

template<typename T>
std::vector<T> keep(
        const std::vector<T> &original,
        std::function<bool(const T&)> useful)
{
    std::vector<T> out;
    for(T item:original)
    {
        if(useful(item))
            out.push_back(item);
    }
    return out;
}

int main()
{
    std::vector<int> a={4,6,2,-5,3,-8,13,-11,27};
    a=keep(a,[](const int& x)->bool{return x>0;});
    for(int y:a)
    {
        std::cout<<y<<std::endl;
    }
    return 0;
}

And this is the error message:

error: no matching function for call to ‘keep(std::vector<int>&, main()::<lambda(const int&)>)’
     a=keep(a,[](const int& x)->bool{return x>0;});
                                                 ^

Solution

  • Change the function keep to

    template<typename T, typename Func>
    std::vector<T> keep(const std::vector<T> &original,
                        Func useful)
    {
        // code as usual
    }
    

    Live example.

    This works with an argument to useful being any one of these:

    • lambda
    • std::function
    • functor
    • function pointer

    From the documentation:

    The lambda expression constructs an unnamed prvalue temporary object of unique unnamed non-union non-aggregate type, known as closure type.

    This means that two lambdas with the same code, would generate two different typed objects.

    auto f1 = [](int) { return true; };
    auto f2 = [](int) { return false; };
    f2 = f1;                               // error: no viable '='
    

    However, both of these are implicitly convert-able to the corresponding std::function types:

    std::function<bool(int)> fn = f1;
    fn = f2;
    

    But then why doesn't it work in your case? This is because of template type deduction. Changing keep to

    template<typename T>
    std::vector<T> keep(const std::vector<T> &original,
                        std::function<bool(const int &)> useful)
    // no type deduction for std::function's template, explicitly mentioned
    

    will make your example compile without any cast at the caller site.

    However, trying to match it against std::function<T> won't work since template type deduction doesn't consider any conversion. Template argument deduction looks for exact type matches. Implicit conversions don't matter at this stage. You've to explicitly cast it to a matching std::function as Atomic_alarm comments. Like Joseph says in How to convert a lambda to an std::function using templates:

    Template type deduction tries to match the type of your lambda function to the std::function<T> which it just can't do in this case - these types are not the same. Template type deduction doesn't consider conversions between types.

    While in the alternative solution what happens is something like this:

    auto f = [](int i) { return (i >= 0); }
    

    The type of f here is not std::function but some unnamed type deduced like it would for the template parameter Func above.

    If you still want to do it the std::function way, see this answer which does it with an additional template indirection. See this answer and this post for related details.