1

This might look similar to "I cannot pass lambda as std::function", but I'm actually passing the std::function parameter by value, so that problem doesn't apply. I've defined the following function.

template<typename T>
std::vector<T> countSort(const std::vector<T> &v, std::function<int(T)> keyFunc, int n);

The second parameter is an std::function that maps T to int (passed by value).

When calling this, I wanted to use a lambda expression, as follows:

std::vector<int> v;
[...]
v = countSort(v, [](int x) { return x; }, 10);

But the template argument deduction fails, because "main()::<lambda(int)> is not derived from std::function<int(T)>". It does work if I specify the template argument, or if I introduce an intermediate variable of type std::function for the lambda expression:

std::function<int(int)> lambda = [](int x) { return x; };
v = countSort(v, lambda, 10);

Why can't I do the former? I'm giving the compiler the exact same information; if it is able to convert a value of type lambda<int> to std::function<int(int)> when assigning it to a variable, why can't it directly convert from lambda<int> to the parameter type, which is std::function<T(int)>—and taking into account that v is of type std::vector<int>, it should know that T is int? The whole reason I want to use a lambda expression is precisely that, it's an expression, so I should be able to write it inline in the function call argument list, without having to give it a name or assign it to a variable.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Anakhand
  • 2,838
  • 1
  • 22
  • 50
  • 3
    Do you really need a `std::function`? Creating and invoking it involves some overhead. Passing the lambda directly as a templated function parameter (`template ... countSort (..., F&& f, ...)`) allows the compiler to optimize/inline everything together which will result in much better performance, especially if the vector is large. – Erlkoenig Aug 24 '20 at 09:20
  • I prefer `std::function` because I can clearly indicate what the expected signature of the function is. – Anakhand Aug 24 '20 at 09:37
  • Well, even the standard library uses templates instead of `std::function` to improve efficiency, e.g. in the [sort](https://en.cppreference.com/w/cpp/algorithm/sort) function, and documents the expected signature. `std::function` is only really for passing functionals to functions that are not/can't be templates. – Erlkoenig Aug 24 '20 at 09:40

1 Answers1

7

The problem is, template argument deduction doesn't consider implicit conversion (from lambda to std::function), which causes the deduction for T on the 2nd function parameter keyFunc to fail.

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 use std::type_identity (since C++20) to exclude the 2nd function parameter from deduction. e.g.

template<typename T>
std::vector<T> countSort(const std::vector<T> &v, std::function<int(std::type_identity_t<T>)> keyFunc, int n);

BTW: If your compiler doesn't support std::type_identity, it's not hard to make one.

And about how std::type_identity works here, see non-deduced context:

(emphasis mine)

In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

  1. The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:
cigien
  • 57,834
  • 11
  • 73
  • 112
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 1
    FWIW, it's available in std since C++20. – Igor R. Aug 24 '20 at 09:24
  • How does this work? Intuitively, if I had to make a guess, it's that applying some kind of "transformation" to the type `T` (even if it's the identity) "confuses" the compiler / obfuscates the parametrization of the parameter type in terms of template parameters, so that the compiler doesn't attempt to consider it for deduction. Is it more or less this? (If so, it has a very "hacky" feel, but hey, if it works and that's its purpose :-)... ) – Anakhand Aug 24 '20 at 12:38
  • @Anakhand Answer revised. – songyuanyao Aug 24 '20 at 12:50