2

In c++17, I have a template function which takes some kind of lambda as input. However it only recognizes those with explicit types and ones using auto are rejected.

Why this is the case and any way to combine auto variables and template function taking lambda with specified form as input?

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

class C {};
vector<C> vec(2);

// ForEach func requires lambda must take Object with type C
// only, while its return type can vary as the function is templated.
template<typename T>
void ForEach(function<T (C a)>& lambda) {
    std::for_each(begin(vec), end(vec), lambda);
};

int main()
{
    auto
    f_add_display4 = [](C i) {
        };
    std::function<void(C)>
    f_add_display3 = f_add_display4;

    ForEach(f_add_display3);
    // ForEach(f_add_display4); // This line won't compile
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • One possible explanation for this problem, per <>, is that auto captures the transparent "proxy type" instead of the real one, which is the std::function. – tokYoComPleX Dec 23 '21 at 23:39

3 Answers3

3

A std function is not a lambda, and a lambda is not a std function.

Your function takes a std function. So when passed it, it deduces the template arguments to std function. If you pass a lambda, it cannot deduce anything.

Second, std function is a type erasure type, not an interface. Your function attempts to deduce the template argument of the type erasure class. Doing this is an anti pattern.

template<class F>
void ForEach(F lambda) {
  std::for_each(begin(vec), end(vec), lambda);
}

this works.

If you want to restruct the F to accepting some signature, in you can do:

void ForEach(std::invocable<C> auto lambda) {
  std::for_each(begin(vec), end(vec), lambda);
}

Or in :

template<std::invocable<C> F>
void ForEach(F lambda) {
  std::for_each(begin(vec), end(vec), lambda);
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

There are actually two fundamental issues here which cause the deduction to fail:

  1. The first is that the type of a lambda expression is never std::function<Signature> for any signature Signature. However, your function expects a non-const reference argument. As the types differ a conversion is needed which would be a temporary object and temporary objects never bind to non-const reference. You could fix this issue by using a const reference as argument:

    template <typename T>
    void ForEach(function<T(C)> const& lambda) { ... }
    
  2. The other problem is that ForEach takes a conceptually open-ended set of potential arguments. However, the argument you have isn't an immediate match: there is no way to to deduce the type T based on the lambda type to exactly match the function argument. Instead, a conversion is required. The compiler won't try to find what instantion might work as the target of an instantiation, although in this case there is only one choice. The conversion itself would work if you'd specify the target type (and made the previous choice of making the parameter const&:

    ForEach<void>(f_add_display4);
    

I would recommend to not constraint the function to take a function<T(C)> to start with! Compared to using an actual lambda function is most likely a pessimization: while the lambda function is statically typed and can, in general, be optimized well, the same is not true for std::function<Signaure>. While the latter can sometimes be optimized often it isn't. If you want to constraint the function to only accept parameters of type C you can do that with some other approaches, probably involving SFINAE for C++17 compilers or a concept for C++20+.

That is, I'd recommend using

template <typename Fun>
void ForEach(Fun&& lambda) {
    ...
}

... of, if you want to constrain the function using C++20 concepts

template <typename Fun>
    requires requires (Fun lambda, C c) { lambda(c); }
void ForEach(Fun&& lambda) {
    ...
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
1

The type of f_add_display3 is not std::function<void(C)>. It is of some unnamed class type (some class with a void operator()(C i)).

You can convert the lamda to a std::function<void(C)> as you do here:

std::function<void(C)> f_add_display3 = f_add_display4;

Though, template argument deduction does not consider implicit conversions.

If you change the argument to be a const reference, then you can specify the template argument explicitly:

template<typename T>
void ForEach(const function<T (C a)>& lambda) {
      // ...
};
ForEach<void>(f_add_display4); // This line will compile !

Or you drop the unnecessary conversion to std::function entirely:

template <typename F>
void ForEach(F f) {
    std::for_each(begin(vec), end(vec),f);
};
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185