1

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)>'
skypjack
  • 45,296
  • 16
  • 80
  • 161
JE42
  • 4,152
  • 4
  • 36
  • 48
  • `applyFunc([](int d) { return d+d; })` – cpplearner Jul 02 '17 at 13:40
  • 4
    User-defined conversions are not considered during template argument deduction. So `E` cannot be deduced unless the argument is of type `std::function`; merely being convertible to such a type is not sufficient. You could always specify `E` explicitly, as @cpplearner shows. – Igor Tandetnik Jul 02 '17 at 13:41
  • 2
    Look at the related list... "Overloading std::function argument to match lambda", "Template argument deduction for lambdas", etc. _all_ answer your question, no? – ildjarn Jul 02 '17 at 13:42
  • Replace `std::function` with `E` and use something like that: https://stackoverflow.com/questions/9065081/how-do-i-get-the-argument-types-of-a-function-pointer-in-a-variadic-template-cla – Guillaume Racicot Jul 02 '17 at 14:12
  • 1
    Alternatively: `struct x { any a; x():a(){} x(any a):a(a){} template operator T() { if(a.empty()) return {}; return any_cast(a); } };` and you can use `int applyFunc(function f) { return f({}); }` This accepts any lambda and passes the argument correctly as long as the arguments exactly match. – Johannes Schaub - litb Jul 02 '17 at 14:23
  • @JohannesSchaub-litb thank you for the pointing me to any. This looks a lot better ! But not quite yet ! Since I would like to have a bit more restrictions on the argument type. I will now see if i can apply this pattern you mentioned with variant. – JE42 Jul 02 '17 at 17:03
  • Also I don't want explicitly mention the type as template argument. As this introduces duplication which i like to reduce. – JE42 Jul 02 '17 at 17:07
  • Just by the way, `applyFunc(+asLambda);` should work. – HolyBlackCat Jul 02 '17 at 20:01
  • @holyblackcat what does + does do ? – JE42 Jul 03 '17 at 02:41
  • @JE42 It forces conversion to the function pointer. – HolyBlackCat Jul 03 '17 at 12:34
  • @HolyBlackCat i tried to find this conversion in the documentation of [std::function](http://en.cppreference.com/w/cpp/utility/functional/function) wasn't able to find it. Do you have any pointers where I can read up on this conversion ? – JE42 Jul 03 '17 at 18:34
  • 1
    I'm unable to find any references on it. (It makes sense that it's not in the std::function docs, since the + is applied to a lambda, not std::function.) Maybe you should ask a new question? – HolyBlackCat Jul 03 '17 at 19:22
  • Found it: https://stackoverflow.com/questions/18889028/a-positive-lambda-what-sorcery-is-this – JE42 Jul 04 '17 at 02:44

1 Answers1

1

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; });
}
skypjack
  • 45,296
  • 16
  • 80
  • 161