2

If you have the following template for filtering list:

template <typename T>
inline std::list<T> list_filter(std::list<T> const& a, bool (f)(T)) {
    std::list<T> output;
    std::copy_if(a.begin(), a.end(), std::back_inserter(output), f);
    return output;
}

Then try to call it with the lambda inside like:

std::list<int> lst = {1,2,3,4,5};
auto filtered = list_filter(lst, [](int el) -> bool { return (el % 2 == 0); });

It will produce the error with no matching function for call to list_filter(..., std::__cxx11::list<int>)::<lambda(int)>)'.

Is there any way to bypass that restriction without extracting the lambda into the separate function? Why C++ doesn't allow this obvious pattern?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Anton Kochkov
  • 1,117
  • 1
  • 9
  • 25
  • Why do you need to specify the signature for your parameter? Just use a separate template parameter for the callable. – super Sep 01 '20 at 07:02
  • @super for clarity. To avoid the misuse. – Anton Kochkov Sep 01 '20 at 07:12
  • 1
    When the callable is passed to `copy_if` it needs to have the right signature, and if not will produce a compile time error. You don't really gain anything in that regard. – super Sep 01 '20 at 07:15

2 Answers2

6

Implicit conversion (from lambda with no capture-list to function pointer) won't be considered in template argument deduction, which fails to deduce template argument T on the 2nd function argument.

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

You can convert lambda to function pointer explicitly by static_cast, or operator+. e.g.

auto filtered = list_filter(lst, +[](int el) -> bool { return (el % 2 == 0); });

Or use std::type_identity (since C++20) to exclude the 2nd function argument from deduction. e.g.

template <typename T>
inline std::list<T> list_filter(std::list<T> const& a, bool (f)(std::type_identity_t<T>)) {
    ...
}

BTW you can make your own type_identity easily if your compiler doesn't support C++20.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • That is interesting engineering: `std::type_identity_t`. – Const Sep 01 '20 at 06:51
  • 2
    @Const See [non-deduced context](https://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts) for more. – songyuanyao Sep 01 '20 at 06:52
  • 3
    @AntonKochkov Yes, caputring lambdas can't convert to function pointer. You can change the function parameter from function pointer to `std::function`, (same deduction issue as function pointer, solution above is required too, i.e. use `std::function)>`), or as @someprogrammerdude suggested pass the lambda directly. – songyuanyao Sep 01 '20 at 06:59
  • @songyuanyao Could you please give some information about this line: +[](int el) -> bool { return (el % 2 == 0); } – Farhad Sarvari Sep 01 '20 at 08:00
  • @FarhadSarvari See [this post](https://stackoverflow.com/q/18889028/3309790) for more. `operator+` causes non-capture lambda converting to function pointer. – songyuanyao Sep 01 '20 at 08:06
  • 2
    @Const If you're interested, I recently wrote [a brief blog post](https://dfrib.github.io/non-deduced-contexts/) on particularly the use of type identity mapping for controlling template argument deduction by removing arguments by placing them in non-deduced contexts. – dfrib Sep 01 '20 at 08:38
4

Lambdas with captures are not compatible with pointers to functions (which is what the argument f is).

I recommend that you take a hint from the standard library itself when it comes to callable objects: Use template arguments for the whole callable object:

template <typename T, typename F>
inline std::list<T> list_filter(std::list<T> const& a, F f) {
    std::list<T> output;
    std::copy_if(a.begin(), a.end(), std::back_inserter(output), f);
    return output;
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621