Pass lambda expression to lambda argument c++11

Couchy picture Couchy · Jul 15, 2013 · Viewed 33.7k times · Source

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

Answer

David Rodríguez - dribeas picture David Rodríguez - dribeas · Jul 16, 2013

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