Lambdas and Std::Function

I cannot pass lambda as std::function

It's because a lambda function is not a std::function<...>. The type of

auto lambda = [](const std::string& s) { return std::stoi(s); };

is not std::function<int(const std::string&)>, but something unspecified which can be assigned to a std::function. Now, when you call your method, the compiler complains that the types don't match, as conversion would mean to create a temporary which cannot bind to a non-const reference.

This is also not specific to lambda functions as the error happens when you pass a normal function. This won't work either:

int f(std::string const&) {return 0;}

int main()
{
std::vector<int> vec;
C<int> c;
c.func(vec, f);
}

You can either assign the lambda to a std::function

std::function<int(const std::string&)> lambda = [](const std::string& s) { return std::stoi(s); };

,change your member-function to take the function by value or const-reference or make the function parameter a template type. This will be slightly more efficient in case you pass a lambda or normal function pointer, but I personally like the expressive std::function type in the signature.

template<typename T>
class C{
public:
void func(std::vector<T>& vec, std::function<T( const std::string)> f){
//Do Something
}

// or
void func(std::vector<T>& vec, std::function<T( const std::string)> const& f){
//Do Something
}

// or
template<typename F> func(std::vector<T>& vec, F f){
//Do Something
}
};

Lambdas and std::function

Stephan T. Lavavej explains why this doesn't work in this video. Basically, the problem is that the compiler tries to deduce BaseT from both the std::vector and the std::function parameter. A lambda in C++ is not of type std::function, it's an unnamed, unique non-union type that is convertible to a function pointer if it doesn't have a capture list (empty []). On the other hand, a std::function object can be created from any possible type of callable entity (function pointers, member function pointers, function objects).

Note that I personally don't understand why you would want to limit the incoming functors to that specific signature (in addition to the fact that indirection through a polymorphic function wrapper, like std::function, is by far more inefficient than a direct call to a functor (which may even be inlined)), but here's a working version. Basically, it disables argument deduction on the std::function part, and only deduces BaseT from the std::vector argument:

template<class T>
struct Identity{
typedef T type;
};

template<typename BaseT>
vector<BaseT> findMatches(vector<BaseT> search,
typename Identity<function<bool (const BaseT &)>>::type func)
{
vector<BaseT> tmp;

for(auto item : search)
{
if( func(item) )
{
tmp.push_back(item);
}
}

return tmp;
}

Live example on Ideone.

Another possible way would be to not restrict the functor type directly, but indirectly through SFINAE:

template<class T, class F>
auto f(std::vector<T> v, F fun)
-> decltype(bool(fun(v[0])), void())
{
// ...
}

Live example on Ideone.

This function will be removed from the overload set if fun doesn't take an argument of type T& or if the return type is not convertible to bool. The , void() makes f's return type void.

std::functions and lambda function passing

Note that (1) fn is defined as reference (to const); (2) lambda and std::function are not the same type; (3) You can't bind reference to object with different type directly.

For the 1st case,

TestClass t([](std::string str) {std::cout << str << std::endl; });
t.F();

A temporary lambda is created and then converted to std::function which is a temporary too. The temporary std::function is bound to the parameter _f of the constructor and bound to member f. The temporary will be destroyed after this statement, then f becomes dangled, when t.F(); it fails.

For the 2nd case,

fn __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();

A temporary lambda is created and then bound to reference (to const). Then its lifetime is extended to the lifetime of the reference __f, so the code is fine.

For the 3rd case,

auto __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();

lambda is created and then converted to std::function which is a temporary. The temporary std::function is bound to the parameter _f of the constructor and bound to member f. The temporary will be destroyed after this statement, then f becomes dangled, when t.F(); it fails.



(1) You could declare fn as non-reference like typedef std::function<void(std::string)> fn;, then std::function will be copied and every case would work well.

(2) Don't use names begin with double underscore, they're reserved in C++.

Passing a Lambda Expression to std::function in C++

The problem

The problem you have can be reduced to this:

#include <functional>

template <typename ...Args>
void foo(std::function<bool(Args...)>) { }

int main()
{
foo([](int, int) { return true; });
}

which will fail to compile. The reason for this is that the type deduction for the template arguments for std::function fails.

You expect a std::function of some sort to be passed as an argument. Since no std::function object (of whatever exact instantiation) is passed in, the compiler tries to construct a std::function and deduce the template parameter types by calling the constructor of std::function. Here starts the problem. The matching constructor in this case would be:

template <typename F>
function(F f);

You will notice that the constructor itself is also templated. The compiler could successfully deduce F as a lambda, but since F is a template parameter of the constructor, the template parameter of the class itself std::function cannot be deduced.

Furthermore, to quote cppreference on that constructor:

[...] This constructor does not participate in overload resolution
unless f is Callable for argument types Args... and return type R. [...]

This means that the existance of this constructor is based on whether F can be called with the class template arguments Args..., but since those are not explicitly defined and cannot be deduced, this constructor won't be available anyway.

The solution

Since you only use that std::function inside exportSelectedData, simply make it a template parameter alltogether (ditching the std::function part):

template<typename Func, typename ...Args_T>
std::vector<CSingleElement> exportSelectedData(uint32_t u32MutexTimeout, Func compareFn, Args_T const&...) const;

You should also change Args_T&& to Args_T const& since you don't simply forward those arguments but reuse them inside a loop.

Edit

Regarding your follow-up question in the edit: think about what you're doing.

First you declare a lambda:

auto lambda = [u32MaxCmdDuration](const CombinedDictElement& singleElement) { /* ... */ };

Now think about the signature of that lambda. You return a boolean so the return type is bool (so far so good). You take u32MaxCmdDuration in the capture-clause and you take one argument singleElement. Lets remove all the extra qualifiers and look at the signature:

bool(CombinedDictElement) // take a CombinedDictElement and return a bool

Next, we take a look at the call of exportSelectedData:

exportSelectedData(u32MutexTimeout, lambda, u32MaxCmdDuration);

You pass in u32MutexTimeout and lambda which is perfectly fine, the lambda is captued by compareFn. The third argument is u32MaxCmdDuration which is captured by ...compareArgs in your template. Now lets take a look at where you actually invoke the lambda inside exportSelectedData:

if (compareFn(singleElement, compareArgs...)) // ...

What signature do you expect compareFn to have here? If we expand the ...compareArgs pack (again, removing the extra qualifiers for simplicity) the signature looks like this:

bool(CombinedDictElement, unsigned int) // take a CombinedDictElement and an unsigned int and return a bool

Here is the lambda signature again:

bool(CombinedDictElement)

Do you spot the problem? The lambda captures u32MaxCmdDuration as a state capture while exportSelectedData expects it as an parameter (since you passed it into exportSelectedData as an additional argument). The signatures differ from each other, obviously, so we have to change one of them to match the other. This is fairly easy in your case, either

  • change your lambda to take u32MaxCmdDuration as a parameter:

    auto lambda = [](const CombinedDictElement& singleElement, unsigned int u32MaxCmdDuration)

    or

  • remove u32MaxCmdDuration as an additional argument to your exportSelectedData call:

     exportSelectedData(u32MutexTimeout, lambda);

Possible storage waste of storing lambda into std::function

This is the price you pay for not needing to know the type of the function. All std::function<void()>s are interchangeable no matter which lambda they came from. If you want to store lots of the same type of function (with different captures) in a vector, you can make it a functor instead of a lambda (so that it has a name) and make a vector of that type.

Example: With lambda:

std::vector<std::function<void()>> funcs;
for(int i = 0; i < 10000; i++)
funcs.push_back([i]() {std::cout << i << std::endl;});
for(auto& func : funcs)
func();

With functor:

struct number_printing_function {
int i;
number_printing_function(int i) : i(i) {}
void operator()() {
std::cout << i << std::endl;
}
};

std::vector<number_printing_function> funcs;
for(int i = 0; i < 10000; i++)
funcs.push_back(number_printing_function(i));
// or funcs.emplace_back(i);

for(auto& func : funcs)
func();

IMO this is a bit useless, because we might as well store a vector of ints and stop pretending they are functions. When you have many functors of the same type you already know what they do, so just do it. Really, the code above is just the code below, but with extra steps:

std::vector<int> ints;
for(int i = 0; i < 10000; i++)
ints.push_back(i);
for(auto& i : ints)
std::cout << i << std::endl;

If lambdas don't have a specified type, how does std::function accept a lambda?

The type is there. It’s just that you don’t know in advance what it is. Lambdas have type - just the standard says nothing about what that type is; it only gives the contracts that type has to fulfill. It’s up to the compiler implementers to decide what that type really is. And they don’t have to tell you. It’s not useful to know.

So you can deal with it just like you would deal with storage of any “generic” type. Namely: provide suitably aligned storage, then use placement new to copy-construct or move-construct the object in that storage. None of it is specific to std::function. If your job was to write a class that can store an arbitrary type, you’d do just that. And it’s what std::function has to do as well.

std::function implementers usually employ the small-object optimization. The class leaves some unused room in its body. If the object to be stored is of an alignment for which the empty room is suitable, and if it will fit in that unused room, then the storage will come from within the std::function object itself. Otherwise, it’ll have to dynamically allocate the memory for it. That means that e.g. capture of intrinsic vector types (AVX, Neon, etc) - if such is possible - will usually make a lambda unfit for small object optimization storage within std::function.

I'm making no claims as to whether or if the capture of intrinsic vector types is allowed, fruitful, sensible, or even possible. It's sometimes a minefield of corner cases and subtle platform bugs. I don't suggest anyone go and do it without full understanding of what's going on, and the ability to audit the resulting assembly as/when needed (implication: under pressure, typically at 4AM on the demo day, with the suits about to wake up soon - and already irked that they have to interrupt their golf play so early in the day just to watch the presenter sweat).

Passing a lambda argument to a std::function parameter without intermediate variable

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:

What's the difference between lambda and std::function?

The first f (i.e., the one designated with auto) results to what is called a lambda function. Also knows as a closure. Closures are unnamed function objects. That's why we need auto to deduce the type of a closure. We don't know it's type but the compiler does. Thus, by using auto we let the compiler deduce the type of the unnamed closure object for us.

The second f (i.e., the one designated with std::function) is a std::function object. Class std::function is a general-purpose polymorphic function wrapper.

Lambdas closures as function objects can be converted to their respective std::function objects. That is exactly what is happening in:

std::function<void(int, int)> f = [](int some, int some2) {
//do something
}

The lambda closure on the right hand side is assigned and converted to the std::function object on the left side of the assignment.

Practically, they're both interpreted as functors, since they both overload call operator() and thus can be called, except for that the lambda's type is unnamed.

Another difference between those two is that you can't assign between lambda closures, since for lambda closures the assignment operator is declared deleted. while you can assign between std::function objects.

C++ lambda as std::function in template function

What about simply as follows ?

template <typename F>
auto Context::executeTransient (F const & commands) {
...
auto result = commands(commandBuffer);
...
return result;
}

This way your method accept both standard functions and lambdas (without converting them to standard functions, that is preferable, from the performance point of view (as far as I know)) and the return type is deduced from the use (auto).

In you need to know the R type inside the method, you can apply decltype() to result

     auto result = commands(commandBuffer);

using R = decltype(result);

If you need to know the R type as template parameter of the method, its a little more complex because involve std::declval() and, unfortunately, add redundancy

template <typename F,
typename R = decltype(std::declval<F const &>()(commandBuffer))>
R Context::executeTransient (F const & commands) {
...
R result = commands(commandBuffer);
...
return result;
}

Call a function with std::function as argument with a lambda

As written in the linked answer, the first version does not compile, because template argument deduction fails for the first argument; a lambda is never an std::function<void(T)>.

The second version compiles, because, by writing {foo}, std::function<void(T)> becomes a non-deduced context. So deduction can no longer fail for the first argument, as it isn't even attempted. So T is deduced as int solely from the second argument, and the call succeeds, because the lambda is convertible to an std::function<void(int)>.

From [temp.deduct.type]:

The non-deduced contexts are:

  • ...
  • A function parameter for which the associated argument is an initializer list but the parameter
    does not have a type for which deduction from an initializer list is specified.


Related Topics



Leave a reply



Submit