Capturing Local Variables in a Lambda Function

So, I wanted to learn a little bit more about Lambdas and their capture capabilities. I started the Lambda with '[=]' to capture all the in-scope variables by value. Without trying to use any of the local variables captured by the Lambda, this works as expected:

using AddNumber = int32_t (*) (int32_t);

AddNumber setAddValue(int32_t x);

void setup() {
  AddNumber functPointer;

  Serial.begin(115200);
  delay(1000);

  functPointer = setAddValue(10);
  Serial.println(functPointer(200));
  Serial.println(functPointer(-100));
}

void loop() {}

AddNumber setAddValue(int32_t x) {
  AddNumber tempPtr;

  tempPtr = [ = ] (int32_t y) {
    return y + 1;
  };
  return tempPtr;
}

Output:

201
-99

But, if I try to use a captured local variable:

using AddNumber = int32_t (*) (int32_t);

AddNumber setAddValue(int32_t x);

void setup() {
  AddNumber functPointer;

  Serial.begin(115200);
  delay(1000);

  functPointer = setAddValue(10);
  Serial.println(functPointer(200));
  Serial.println(functPointer(-100));
}

void loop() {}

AddNumber setAddValue(int32_t x) {
  AddNumber tempPtr;

  tempPtr = [ = ] (int32_t y) {
    return y + x;
  };
  return tempPtr;
}

Then, the compiler complains:

Arduino: 1.8.5 (Windows 10), TD: 1.47, Board: "Arduino/Genuino Uno"

C:\Users\tr001221\AppData\Local\Temp\arduino_modified_sketch_816404\sketch_oct09c.ino: In function 'int32_t (* setAddValue(int32_t))(int32_t)':

sketch_oct09c:21: error: cannot convert 'setAddValue(int32_t)::<lambda(int32_t)>' to 'AddNumber {aka long int (*)(long int)}' in assignment

   tempPtr = [ = ] (int32_t y) {

           ^

exit status 1
cannot convert 'setAddValue(int32_t)::<lambda(int32_t)>' to 'AddNumber {aka long int (*)(long int)}' in assignment

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Can you not use local-scope variables captured by a Lambda? Or, am I just doing it wrong?

Thanks.

Your variable x is only valid inside the scope of the setAddValue() function. The use of a lambda function doesn't extend that scope. If you define the lambda function inside setup() and use it there, you can use a variable that is still inside it's scope there.

pylon:
Your variable x is only valid inside the scope of the setAddValue() function. The use of a lambda function doesn't extend that scope. If you define the lambda function inside setup() and use it there, you can use a variable that is still inside it's scope there.

That's not my (perhaps incorrect) understanding. From multiple Google searches (ex C++11 Lambda: Capture Variables by Reference or Value - thisPointer), it appears to me that the Lambda can capture a local variable as long as it's in-scope at the time the Lambda is created. It then extends the lifetime of that variable and makes it in-scope within the Lambda for as long as the Lambda exists.

Also, here: Closure (computer programming) - Wikipedia

Mind that I'm not an expert on lambda.
But lambda and function pointer are not the same thing thus, you cannot assign it like the second case.

The first case, the code got optimized by the compiler to "work".

These are just my guesses.

Capturing lambdas are callable functor objects, they cannot be converted into function pointers.

"Normal" functions don't have any data associated with them, but a lambda has to save its captures.

If a lambda doesn't capture anything, you can convert it to a function pointer, because it doesn't need any data:

expr.prim.lambda.closure:
The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (10.5) having the same parameter and return types as the closure type’s function call operator. [...] The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type’s function call operator.

Pieter

arduino_new:
But lambda and function pointer are not the same thing thus, you cannot assign it like the second case.

That's exactly how Lambdas are used many times -- as arguments to functions that take a function pointer.

Nope, as far as I can tell, both assigning a Lambda to a function pointer and capturing a Global variable work just fine:

using AddNumber = int32_t (*) (int32_t);

int32_t adder;

AddNumber setAddValue();

void setup() {
  AddNumber functPointer;

  Serial.begin(115200);
  delay(1000);

  adder = 10;
  functPointer = setAddValue();
  Serial.println(functPointer(200));
  Serial.println(functPointer(-100));

  adder = 20;
  functPointer = setAddValue();
  Serial.println(functPointer(200));
  Serial.println(functPointer(-100));
}

void loop() {}

AddNumber setAddValue() {
  AddNumber tempPtr;

  tempPtr = [ = ] (int32_t y) {
    return y + adder;
  };
  return tempPtr;
}

Output:

210
-90
220
-80

What won't compile is capturing a local variable.

gfvalvo:
capturing a Global variable work just fine

Global variables are not captured.

In fact, your code doesn't compile for me. It does work if I remove the "=" from the capture list.

Did you see the edit I made to my previous post?

PieterP:
In fact, your code doesn't compile for me. It does work if I remove the "=" from the capture list.

That's interesting. My code from Reply #6 compiles without complaint for both Uno and Teensy 3.2 using Arduino v1.8.5. That's a mystery for another day.

Did you see the edit I made to my previous post?

Yes, that quote reads like typical Standards Body Mumbo Jumbo -- not really meant for anybody to learn from. Does it mean that if the Lambda does not capture any local variables then you can assign it to a function pointer?

If that's the case, could I trouble you to show a simple example of doing something useful with a Lambda that does capture local variables? Thanks!!!!

gfvalvo:
Does it mean that if the Lambda does not capture any local variables then you can assign it to a function pointer?

Correct. Otherwise, where would it save the copies of or references to the local variables?

gfvalvo:
That's interesting. My code from Reply #6 compiles without complaint for both Uno and Teensy 3.2 using Arduino v1.8.5. That's a mystery for another day.

That's probably because I'm using a newer version of the Arduino AVR Core. They finally updated their compiler to GCC 7.
But they still compile everything using C++11, even though GCC 7 supports C++14 and most of C++17 :frowning:

[color=#434f54]/// Signum function: [/color][u][color=#434f54]https://stackoverflow.com/a/4609795[/color][/u]
[color=#5e6d03]template[/color] [color=#434f54]<[/color][color=#5e6d03]typename[/color] T[color=#434f54]>[/color] 
[color=#00979c]int[/color] sgn(T val) { [color=#5e6d03]return[/color] (T(0) [color=#434f54]<[/color] val) [color=#434f54]-[/color] (val [color=#434f54]<[/color] T(0)); }

[color=#434f54]/// Bisection method for rootfinding:[/color]
[color=#434f54]/// Passing a functor as an argument to another function[/color]
[color=#5e6d03]template[/color] [color=#434f54]<[/color][color=#00979c]class[/color] Functor[color=#434f54]>[/color]
[color=#00979c]float[/color] findRoot(Functor func, [color=#00979c]float[/color] lowerbound, [color=#00979c]float[/color] upperbound, [color=#00979c]float[/color] tolerance [color=#434f54]=[/color] 1e-3, [color=#00979c]unsigned[/color] [color=#00979c]int[/color] maxIterations [color=#434f54]=[/color] 100) {
  [color=#5e6d03]while[/color] (maxIterations [color=#434f54]--[/color][color=#434f54]>[/color] 0) {  
    [color=#00979c]float[/color] midPoint [color=#434f54]=[/color] (lowerbound [color=#434f54]+[/color] upperbound) [color=#434f54]/[/color] 2;
    [color=#5e6d03]if[/color] ((upperbound [color=#434f54]-[/color] lowerbound) [color=#434f54]/[/color] 2 [color=#434f54]<[/color] tolerance [color=#434f54]||[/color] func(midPoint) [color=#434f54]==[/color] 0)
      [color=#5e6d03]return[/color] midPoint;
    [color=#5e6d03]if[/color] (sgn(func(midPoint)) [color=#434f54]==[/color] sgn(func(lowerbound)))
      lowerbound [color=#434f54]=[/color] midPoint;
    [color=#5e6d03]else[/color]
      upperbound [color=#434f54]=[/color] midPoint;  
  }
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color](F([color=#005c5f]"Bisection failed to reach tolerance, increase number of iterations"[/color]));
  [color=#5e6d03]return[/color] (lowerbound [color=#434f54]+[/color] upperbound) [color=#434f54]/[/color] 2;
}

[color=#434f54]/// A lambda returning another lambda[/color]
[color=#00979c]auto[/color] makeQuadraticPolynomial [color=#434f54]=[/color] [[color=#000000]]([/color][color=#00979c]float[/color] a0, [color=#00979c]float[/color] a1, [color=#00979c]float[/color] a2) {
  [color=#5e6d03]return[/color] [[color=#434f54]=[/color][color=#000000]]([/color][color=#00979c]float[/color] x) { 
    [color=#5e6d03]return[/color] a2 [color=#434f54]*[/color] x [color=#434f54]*[/color] x [color=#434f54]+[/color] a1 [color=#434f54]*[/color] x [color=#434f54]+[/color] a0;  
  }; 
};

[color=#5e6d03]#if[/color] __cplusplus [color=#434f54]>=[/color] 201402L
[color=#434f54]/// In C++14, you can easily return lambdas from normal functions[/color]
[color=#00979c]auto[/color] makeQuadraticPolynomialCpp14([color=#00979c]float[/color] a0, [color=#00979c]float[/color] a1, [color=#00979c]float[/color] a2) {
  [color=#5e6d03]return[/color] [[color=#434f54]=[/color][color=#000000]]([/color][color=#00979c]float[/color] x) { 
    [color=#5e6d03]return[/color] a2 [color=#434f54]*[/color] x [color=#434f54]*[/color] x [color=#434f54]+[/color] a1 [color=#434f54]*[/color] x [color=#434f54]+[/color] a0;  
  }; 
}
[color=#5e6d03]#endif[/color]

[color=#00979c]float[/color] goldenQuadratic([color=#00979c]float[/color] x) {
  [color=#5e6d03]return[/color] x [color=#434f54]*[/color] x [color=#434f54]-[/color] x [color=#434f54]-[/color] 1; 
}

[color=#00979c]void[/color] [color=#5e6d03]setup[/color]() {
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color](115200);
  [color=#5e6d03]while[/color] ([color=#434f54]![/color][b][color=#d35400]Serial[/color][/b]);

  [color=#434f54]// Create a lambda function that evaluates a polynomial[/color]
  [color=#00979c]float[/color] a2 [color=#434f54]=[/color] 2, a1 [color=#434f54]=[/color] 0, a0 [color=#434f54]=[/color] [color=#434f54]-[/color]4;
  [color=#00979c]auto[/color] polynomial1 [color=#434f54]=[/color] [[color=#434f54]=[/color][color=#000000]]([/color][color=#00979c]float[/color] x) {
    [color=#5e6d03]return[/color] a2 [color=#434f54]*[/color] x [color=#434f54]*[/color] x [color=#434f54]+[/color] a1 [color=#434f54]*[/color] x [color=#434f54]+[/color] a0;
  };
  [color=#00979c]float[/color] root1 [color=#434f54]=[/color] findRoot(polynomial1, 0, 2, 1e-6);
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]print[/color]([color=#005c5f]"The first polynomial has a root in "[/color]);
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color](root1, 6);

  [color=#434f54]// Create a lambda function that saves the coefficients[/color]
  [color=#00979c]auto[/color] polynomial2 [color=#434f54]=[/color] makeQuadraticPolynomial([color=#434f54]-[/color]3, 0, 1);
  [color=#00979c]float[/color] root2 [color=#434f54]=[/color] findRoot(polynomial2, 0, 2, 1e-6);
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]print[/color]([color=#005c5f]"The second polynomial has a root in "[/color]);
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color](root2, 6);

  [color=#434f54]// You can also use a normal function pointer[/color]
  [color=#00979c]float[/color] root3 [color=#434f54]=[/color] findRoot(goldenQuadratic, 1, 2, 1e-6);
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]print[/color]([color=#005c5f]"Φ = "[/color]);
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color](root3, 6);
}

[color=#00979c]void[/color] [color=#5e6d03]loop[/color]() {}

On platforms that have access to the STL, you can create an std::function from a lambda. This has some overhead, though, because the actual functor inside of the std::function is type-erased, IIRC.
This is used extensively in the ESP8266 and ESP32 cores for callbacks etc.

Thanks, I know you tried. But I did ask for a simple example, along the complexity of what I originally posted (but actually works).

Ah, I misunderstood the "useful" part of your question, I thought you meant a real-life example :slight_smile:

Here you go:

[color=#00979c]auto[/color] [color=#000000]makeAdder[/color] [color=#434f54]=[/color] [color=#000000][[/color][color=#000000]][/color] [color=#000000]([/color][color=#00979c]int[/color] [color=#000000]adder[/color][color=#000000])[/color] [color=#000000]{[/color]
  [color=#5e6d03]return[/color] [color=#000000][[/color][color=#434f54]=[/color][color=#000000]][/color] [color=#000000]([/color][color=#00979c]int[/color] [color=#000000]y[/color][color=#000000])[/color] [color=#000000]{[/color]
    [color=#5e6d03]return[/color] [color=#000000]y[/color] [color=#434f54]+[/color] [color=#000000]adder[/color][color=#000000];[/color]
  [color=#000000]}[/color][color=#000000];[/color]
[color=#000000]}[/color][color=#000000];[/color]

[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color][color=#000000]([/color][color=#000000]115200[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#5e6d03]while[/color] [color=#000000]([/color][color=#434f54]![/color][b][color=#d35400]Serial[/color][/b][color=#000000])[/color][color=#000000];[/color]

  [color=#00979c]auto[/color] [color=#000000]adder1[/color] [color=#434f54]=[/color] [color=#000000]makeAdder[/color][color=#000000]([/color][color=#000000]10[/color][color=#000000])[/color][color=#000000];[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000]adder1[/color][color=#000000]([/color][color=#000000]200[/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000]adder1[/color][color=#000000]([/color][color=#434f54]-[/color][color=#000000]100[/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color]

  [color=#00979c]auto[/color] [color=#000000]adder2[/color] [color=#434f54]=[/color] [color=#000000]makeAdder[/color][color=#000000]([/color][color=#000000]20[/color][color=#000000])[/color][color=#000000];[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000]adder2[/color][color=#000000]([/color][color=#000000]200[/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000]adder2[/color][color=#000000]([/color][color=#434f54]-[/color][color=#000000]100[/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color]
[color=#000000]}[/color]

[color=#00979c]void[/color] [color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color][color=#000000]}[/color]

The makeAdder function has to be a lambda itself in C++11, which is really awkward. In C++14 (I think Teensy should support this, or you can just change "-std=gnu++11" to "-std=gnu++14" in your platform.txt file), you can do the following:

[color=#00979c]auto[/color] [color=#000000]makeAdder[/color][color=#000000]([/color][color=#00979c]int[/color] [color=#000000]adder[/color][color=#000000])[/color] [color=#000000]{[/color]
  [color=#5e6d03]return[/color] [color=#000000][[/color][color=#434f54]=[/color][color=#000000]][/color] [color=#000000]([/color][color=#00979c]int[/color] [color=#000000]y[/color][color=#000000])[/color] [color=#000000]{[/color]
    [color=#5e6d03]return[/color] [color=#000000]y[/color] [color=#434f54]+[/color] [color=#000000]adder[/color][color=#000000];[/color]
  [color=#000000]}[/color][color=#000000];[/color]
[color=#000000]}[/color]