Pass lambda expression to lambda argument c++11

2020-05-19 05:55发布

I would like to do something like this:

int main()
{
    auto f = [/*some variables*/](/*take lambda function*/)
    {/*something with lambda function*/};

    f([/*other variables*/](/*variables to be decided by f()*/)
    {/*something with variables*/});
}

I know that it is possible to pass a lambda to a function, as well as to a lambda. The following works:

int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](double (func)(int)) -> double
    {func(0); return 0.0;};

    f([](int i) -> double
    {return 0.0;});
}

But the following does not work (as soon as i change the scope variables to add [x])

int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](double (func)(int)) -> double
    {func(0); return 0.0;}

    f([x](int i) -> double    //[x] does not work
    {return 0.0;});
}

which gives the error:

error: function "lambda [](double (*)(int))->double::operator()" cannot be called with the given argument list
        argument types are: (lambda [](int)->double)
        object type is: lambda [](double (*)(int))->double

would anyone have an idea as to how to fix this, or a way around it? I am using the intel compiler icpc (ICC) 13.1.2 with std=c++11

Thanks

5条回答
疯言疯语
2楼-- · 2020-05-19 06:34

There are a couple of things to clarify regarding your question. The first of which is what is a lambda?

A lambda expression is a simple expression from which the compiler will generate a unique type that cannot be named, and at the same time it will generate an instance of the type. When you write: [](int i) { std::cout << i; } the compiler will generate a type for you that is roughly:

struct __lambda_unique_name {
   void operator()(int i) const { std::cout << i; }
};

As you can see, it is not a function, but a type that implements operator() as a const member function. If the lambda did any capture, the compiler would generate code to capture the value/references.

As a corner case, for lambdas like the above, where there is no state being captured, the language allows for a conversion from the lambda type to a pointer to function with the signature of the operator() (minus the this part), so the lambda above can be implicitly converted to a pointer to function taking int and returning nothing:

void (*f)(int) = [](int i) { std::cout << i; }

Now that the basics have been stated, in your code you have this lambda:

auto f = [x,y](double (func)(int)) -> double {func(0); return 0.0;};

The rules for parameters to functions (that also apply to lambdas) determine that an argument cannot be of type function, so the argument to the lambda decays to a pointer to function (in the same way that an argument of type array decays to a pointer type):

auto f = [x,y](double (*func)(int)) -> double {func(0); return 0.0;};

At a later point you are trying to pass a lambda that has a capture as an argument. Because there is a capture, the special rule does not apply and the lambda is not convertible to a pointer to function yielding the compiler error that you see.

In the current standard you can go one of two ways. You can use type-erasure to remove the exact type of the callable entity from the signature:

auto f = [x,y](std::function<double(int)> func) -> double {func(0); return 0.0;};

Because a std::function<double(int)> can be initialized with any callable entity with the appropriate signature, this will accept the lambdas in the code below, at the cost of type-erasure that usually implies a dynamic allocation and dynamic dispatch.

Alternatively, you can drop the syntactic sugar and roll the first lambda equivalent manually, but make it generic. In this case, where the lambda is simple this could be a valid option:

struct mylambda {
   template <typename F>
   double operator()(F fn) const {
      fn(0); return 0.0;
   }
} f;
// then use the non-lambda as you tried:
f([x](int i) -> double {return 0.0;});

Finally, if you are patient enough, you can wait for C++14, where (most probably, it has not yet been ratified) there will be support for polymorphic lambdas which simplify the creation of the above class:

auto f = [](auto fn) { fn(0.0); return 0.0; } // unrolls to 'mylambda' above
查看更多
来,给爷笑一个
3楼-- · 2020-05-19 06:41

You may have to simply bite the bullet and implement your own functors like we did in the dark ages:

struct F {
    int x;
    int y;

    F(int x_, int y_) : x(x_), y(y_) {}

    template <typename G>
    double operator() (G&& g) const {
        g(0);
        return 0.0;
    }
};

#include <iostream>

int main()
{
    int x = 0;
    int y = 0;
    auto f = F(x, y);

    f([x](int i){return 0.0;});
    f([](int i){std::cout << i << std::endl;});
}

That should keep you going until your compiler supports C++14 generic lambdas.

查看更多
Viruses.
4楼-- · 2020-05-19 06:42

You could try something like the following if you know the type of the lambda beforehand, for instance:

int main()
{
    int x = 0, y = 0;

    auto f = [x]( int i )->double {
        return (double)x;
    };

    auto f2 = [x,y]( decltype(f) func )->double {
        return func( 0 );
    };

    f2( f );

    return 0;
}

Or alternative you could use the <functional> library for a more generic solution, for instance:

auto f = [x,y]( std::function<double(int)> func ) { /* Do stuff */ };
查看更多
甜甜的少女心
5楼-- · 2020-05-19 06:46

Try using std::function:

#include <functional>
int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](std::function<double(int)> func) -> double
             {func(0); return 0.0;};

    f([x](int i) -> double {return 0.0;});
}
查看更多
闹够了就滚
6楼-- · 2020-05-19 06:52

You can cify a capturing lambda, but this solution has its limitations:

#include <new>

#include <utility>

namespace
{

template <typename F, int I, typename L, typename R, typename ...A>
inline F cify(L&& l, R (*)(A...) noexcept(noexcept(
  std::declval<F>()(std::declval<A>()...))))
{
  static L l_(std::forward<L>(l));
  static bool full;

  if (full)
  {
    l_.~L();

    new (static_cast<void*>(&l_)) L(std::forward<L>(l));
  }
  else
  {
    full = true;
  }

  return [](A... args) noexcept(noexcept(
      std::declval<F>()(std::forward<A>(args)...))) -> R
    {
      return l_(std::forward<A>(args)...);
    };
}

}

template <typename F, int I = 0, typename L>
inline F cify(L&& l)
{
  return cify<F, I>(std::forward<L>(l), F());
}


int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](double (func)(int)) -> double
    {func(0); return 0.0;};

    f(cify<double(*)(int i)>([x](int i) -> double    //works now
    {return 0.0;}));
}

Click for a working example.

查看更多
登录 后发表回答