JE42
JE42

Reputation: 5149

template with std::function doesn't directly match lambda

How can I best define the template function applyFunc to match lambda's the signature ?

I don't want to define a generic template argument capturing the function type as a whole, since I would like to use the type E of the argument of the function passed in.

#include <functional>
template<class E>
int applyFunc(std::function<int(E)> f) {
    return f(E{});   
}

int main()
{
    auto asLambda = [](int d) -> int {  return d+d; };
    std::function<int(int)> asFunc = asLambda;

    applyFunc(asLambda); //this doesn't :(
    applyFunc(asFunc); //this works
}

Compilation fails with:

15:23: error: no matching function for call to 'applyFunc(main()::<lambda(int)>&)'
15:23: note: candidate is:
6:5: note: template<class E> int applyFunc(std::function<int(E)>)
6:5: note:   template argument deduction/substitution failed:
15:23: note:   'main()::<lambda(int)>' is not derived from 'std::function<int(E)>'

Upvotes: 2

Views: 659

Answers (1)

skypjack
skypjack

Reputation: 50540

How can I best define the template function applyFunc to match lambda's the signature ?

As long as you accept to use non capturing lambdas only (as you did in the example code), you can exploit the fact that they decay to function pointers.
As a minimal, working example:

template<class E>
int applyFunc(int(*f)(E)) {
    return f(E{});   
}

int main() {
    auto asLambda = [](int d) -> int {  return d+d; };
    applyFunc(+asLambda);
}

If you want to use capturing lambdas instead, you can extract the type E somehow if you accept the followings:

  • You must use a generic type F instead of a std::function
  • You cannot use generic lambdas

Then, you can look directly at the operator() of your lambda.
It follows a minimal, working example:

template<typename F>
struct GetFrom {
    template<typename R, typename E>
    static E typeE(R(F::*)(E) const);

    // required for mutable lambdas
    template<typename R, typename E>
    static E typeE(R(F::*)(E));
};

template<class F>
int applyFunc(F f) {
    using E = decltype(GetFrom<F>::typeE(&F::operator()));
    return f(E{});   
}

int main() {
    int i = 0;
    applyFunc([i](int d) mutable -> int {  return d+d; });
    applyFunc([i](int d) -> int {  return d+d; });
}

You can easily extend it to multiple arguments if you need. Use a std::tuple as a return type and get the i-th type from it.

Finally, if you want to use capturing lambdas and assign them to a std::function for whatever reason, be aware that E cannot be automatically deduced and thus you have to explicitly specify it (as suggested in the comments to the question by @cpplearner):

applyFunc<int>([](int d) { return d+d; })

EDIT

GetFrom can also be used directly in SFINAE expressions, as requested in the comments.
As a minimal, working example:

#include<type_traits>

template<typename F>
struct GetFrom {
    template<typename R, typename E>
    static E typeE(R(F::*)(E) const);

    // required for mutable lambdas
    template<typename R, typename E>
    static E typeE(R(F::*)(E));
};

template<class F, typename E = decltype(GetFrom<F>::typeE(&F::operator()))>
std::enable_if_t<std::is_same<E, int>::value, E>
applyFunc(F f) {
    return f(E{});   
}

template<class F, typename E = decltype(GetFrom<F>::typeE(&F::operator()))>
std::enable_if_t<std::is_same<E, double>::value, E>
applyFunc(F f) {
    return f(E{});   
}

int main() {
    int i = 0;
    applyFunc([i](int d) mutable -> int {  return d+d; });
    applyFunc([i](double d) -> int {  return d+d; });
    // this won't compile, as expected
    // applyFunc([i](char d) -> int {  return d+d; });
}

Upvotes: 2

Related Questions