How to Convert a Lambda to an Std::Function Using Templates

How to convert a lambda to an std::function using templates

You can't pass a lambda function object as an argument of type std::function<T> without explicitly specifying the template argument T. 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.

It is possible if you can give it some other way to deduce the type. You can do this by wrapping the function argument in an identity type so that it doesn't fail on trying to match the lambda to std::function (because dependent types are just ignored by type deduction) and giving some other arguments.

template <typename T>
struct identity
{
typedef T type;
};

template <typename... T>
void func(typename identity<std::function<void(T...)>>::type f, T... values) {
f(values...);
}

int main() {
func([](int x, int y, int z) { std::cout << (x*y*z) << std::endl; }, 3, 6, 8);
return 0;
}

This is obviously not useful in your situation though because you don't want to pass the values until later.

Since you don't want to specify the template parameters, nor do you want to pass other arguments from which the template parameters can be deduced, the compiler won't be able to deduce the type of your std::function argument.

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;
}

Template to convert any lambda function (including capturing lambdas) to a std::function object

If you want to convert a callable object to a std::function without specifying the signature of the std::function, this is exactly what C++17's deduction guides for std::function are for. We just need to implement a version of that for C++11. Note that this only works for callables with a non-overloaded operator(); otherwise, there is no way to do this.

#include <functional>
#include <utility> // std::declval

// Using these functions just for the return types, so they don't need an implementation.

// Support function pointers
template <typename R, typename... ArgTypes>
auto deduce_std_function(R(*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;

// Support callables (note the _impl on the name).
// Many overloads of this to support different const qualifiers and
// ref qualifiers. Technically should also support volatile, but that
// doubles the number of overloads and isn't needed for this illustration.
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;

template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const) -> std::function<R(ArgTypes...)>;

template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) &) -> std::function<R(ArgTypes...)>;

template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const&) -> std::function<R(ArgTypes...)>;

template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) &&) -> std::function<R(ArgTypes...)>;

template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const&&) -> std::function<R(ArgTypes...)>;

// To deduce the function type for a callable, get its operator() and pass that to
// the _impl functions above.
template <typename Function>
auto deduce_std_function(Function)
-> decltype(deduce_std_function_impl(&Function::operator()));

template <typename Function>
using deduce_std_function_t = decltype(deduce_std_function(std::declval<Function>()));

template <typename F>
auto to_std_function(F&& fn) -> deduce_std_function_t<F> {
return deduce_std_function_t<F>(std::forward<F>(fn));
}

Demo



A more detailed explanation

We need to deduce the function type for the std::function<...>. So we need to implement some kind of deduce_std_function that figures out the function type. There are several options for implementing this:

  • Make a function_traits type which figures out the function type for us (similar to your lambda_traits).
  • Implement deduce_std_function as an overload set, where the return type of the overloads is the deduced type.

I chose the latter because it mimics deduction guides. The former would work too, but I thought that this method might be easier (function boilerplate is smaller than struct boilerplate).

The easy case

Looking at the documentation for std::function's deduction guides, there is an easy one:

template<class R, class... ArgTypes>
function(R(*)(ArgTypes...)) -> function<R(ArgTypes...)>;

That can be easily translated:

template <typename R, typename... ArgTypes>
auto deduce_std_function(R(*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;

Basically, given any function pointer R(*)(ArgTypes...), the type we want is std::function<R(ArgTypes...)>.

The trickier case

The documentation states the second case as:

This overload only participates in overload resolution if
&F::operator() is well-formed when treated as an unevaluated operand
and decltype(&F::operator()) is of the form R(G::*)(A...) (optionally
cv-qualified, optionally noexcept, optionally lvalue reference
qualified) for some class type G. The deduced type is
std::function<R(A...)>.

That's a mouthful. However, the key idea here is the pieces:

  • "decltype(&F::operator()) is of the form R(G::*)(A...)"
  • "The deduced type is std::function<R(A...)>"

This means that we need to get the pointer-to-member-function for operator() and use the signature of that pointer-to-member-function as the signature of the std::function.

That's where this comes from:

template <typename Function>
auto deduce_std_function(Function)
-> decltype(deduce_std_function_impl(&Function::operator()));

We delegate to deduce_std_function_impl because we need to deduce the signature for the pointer-to-member-function &Function::operator().

The interesting overload of that impl function is:

template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;

In short, we're grabbing the signature (the R ... (ArgTypes...) bit) of the pointer-to-member-function and using that for std::function. The remaining part of the syntax (the (F::*) bit) is just the syntax for a pointer-to-member-function. R(F::*)(ArgTypes...) is the type of a pointer-to-member-function for class F with the signature R(ArgTypes...) and no const, volatile, or reference qualifiers.

But wait! We want to support const and reference qualifiers (you may wish to add support for volatile too). So we need to duplicate the deduce_std_function_impl above, once for each qualifier:



































SignatureClass Declaration
R(F::*)(ArgTypes...)void operator()();
R(F::*)(ArgTypes...) constvoid operator()() const;
R(F::*)(ArgTypes...) &void operator()() &;
R(F::*)(ArgTypes...) const&void operator()() const&;
R(F::*)(ArgTypes...) &&void operator()() &&;
R(F::*)(ArgTypes...) const&&void operator()() const&&;

Constructing std::function argument from lambda

Or can it be that for templates the automatic conversion does not take place at all?

Yes. Implicit conversions won't be considered in template argument deduction.

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

That means given MyFunction([](int){});, the implicit conversion (from lambda to std::function) won't be considered, then the deduction for TReturn and TArgs fails and the invocation attempt fails too.

As the workarounds, you can

  1. Use explicit conversion as you showed
  2. As the comment suggested, just use a single template parameter for functors. e.g.

    template<typename F>
    auto MyFunction2(F&& callback)
    {
    return [callback = std::move(callback)](auto&&... args)
    {
    return callback(std::forward<decltype(args)>(args)...);
    };
    }

Parsing lambda to a function using templates

Don't wrap the template parameter in std::function.

The best way to pass a lambda to a function is to just have it as an unconstrained template parameter:

template<class F>
void testing(F predicate) {
auto a = predicate(2);
std::cout << a << '\n';
}

int main() {
int ax = 2;
testing([ax](int x) { return x == ax; });
}

Benefits over std::function.

  • std::function allocates space on the heap to store the functor
  • std::function has an overhead similar to a virtual function call
  • std::function can't be inlined by the compiler, but it's trivial to inline a lambda that's passed directly

implicit convert from lambda to std::function inside variadic template

The problem is that you have a variadic template, and it starts trying to see "what else" it can deduce besides your explicit char argument.

If you had a single argument template like so:

template<typename Result> using Fun = std::function<void(Result)>;
template<typename Result> void yield(Fun<Result>&& body) {};

You'll notice that

yield<char>(
[](char) -> void {} // 3. fail, seems achievable
);

Has no trouble at all because the entirety of std::function is deducible.

But as soon as we make a variadic template our compilers become unhappy:

template<class... Result> using Fun = std::function<void(Result...)>;
template<class... Result> void yield(Fun<Result...>&& body) {};

This is because, like it or not, the compiler is going to try to deduce more template arguments for Fun<Result...> given the value that was passed in ([temp.deduct.type]).

yield2 sidesteps this issue because it places the resulting type into a non-deduced context, but since you explicitly specify the template arguments, it will use only those explicitly specified arguments (char) to deduce the type (#1 works for basically the same reason).

The best workaround I think is your yield2 attempt, however you may also do something like this to prevent the value being passed from participating in type deduction:

auto fn = &yield<char>;
fn(
[](char) -> void {}
);

Another workaround is to static_cast your call to yield to the proper type:
(Really I'm just reading through other possible workarounds under "The non-deduced contexts are:" for [temp.deduct.type])

using fn_type = void(*)(Fun<char>&&);

static_cast<fn_type>(&yield)(
[](char) -> void {}
);

Edit: Finally, you could write some additional boilerplate templating to make the call look nicer (something approaching your #4). Bear in mind this is an incomplete impl made for example:

The goal of this templating is to detect the lambda's operator() function and extract its return type and arguments so that we can explicitly specify a Fun type when we call yield (extracting the return type is unnecessary since you're only ever using void):

First a helper struct that will allow us to detect an immutable lambda's return type and argument types:

template<class T>
struct Fun_Type;

template<class C, class Ret, class... Args>
struct Fun_Type<Ret(C::*)(Args...) const>
{
using type = Fun<Args...>;
};

Second, our helper function, call_yield, that passes on Fun_Type<...>::type to a call to yield:

template<class ImmutableLambda>
void call_yield(ImmutableLambda&& c)
{
using Fun_t = typename Fun_Type<decltype(&ImmutableLambda::operator())>::type;
yield(Fun_t{std::forward<ImmutableLambda>(c)});
}

And now we can call it simply:

int main() {
call_yield(
[](char) -> void {}
);
}

Demo (C++11)

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:

Passing a lambda into a function template

Your function binsearch takes a function pointer as argument. A lambda and a function pointer are different types: a lambda may be considered as an instance of a struct implementing operator().

Note that stateless lambdas (lambdas that don't capture any variable) are implicitly convertible to function pointer. Here the implicit conversion doesn't work because of template substitution:

#include <iostream>

template <typename T>
void call_predicate(const T& v, void (*predicate)(T)) {
std::cout << "template" << std::endl;
predicate(v);
}

void call_predicate(const int& v, void (*predicate)(int)) {
std::cout << "overload" << std::endl;
predicate(v);
}

void foo(double v) {
std::cout << v << std::endl;
}

int main() {
// compiles and calls template function
call_predicate(42.0, foo);

// compiles and calls overload with implicit conversion
call_predicate(42, [](int v){std::cout << v << std::endl;});

// doesn't compile because template substitution fails
//call_predicate(42.0, [](double v){std::cout << v << std::endl;});

// compiles and calls template function through explicit instantiation
call_predicate<double>(42.0, [](double v){std::cout << v << std::endl;});
}

You should make your function binsearch more generic, something like:

template <typename T, typename Predicate>
T binsearch(const std::vector<T> &ts, Predicate p) {

// usage

for(auto& t : ts)
{
if(p(t)) return t;
}

// default value if p always returned false

return T{};
}

Take inspiration from standard algorithms library.

Cast lambda to std::function with parameter pack

The issue is that the compiler doesn't know that you've intended int, int to be the whole of TArgs, and so tries to deduce the remainder of TArgs from the argument f.

For example, this would be valid:

Functor<int, int, int>(std::function<int(int, int, char, float)>{});
// TArgs := {int, int, [...] char, float}

So you need to instruct the compiler to not try to deduce the remainder of TArgs. For example, you could write:

(*Functor<int, int, int>)(x);

Or you could write Functor with a non-decomposed signature Sig:

template <Sig>
void Functor(std::function<Sig> f) {}

Or you could wrap the use of TArgs in the parameter f in a non-deduced context:

template <typename TReturn, typename ... TArgs>
void Functor(std::function<std::conditional_t<false, void, TReturn (TArgs...)>> f) {}

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);


Related Topics



Leave a reply



Submit