2

What is the difference between this line of code (Code 1)

auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };

this line (Code 2)

static int l = 0;
auto l1 = [](auto a) {  std::cout << a << " " << ++l << std::endl; };

and this? (Code 3)

int l = 0;
auto l1 = [l](auto a) mutable {  std::cout << a << " " << ++l << std::endl; };

Main

l1("Joe");
l1("Joo");
l1(1.5);

Sometimes the int variable 'l' is shared between the calls and sometimes it is not. Having auto for one of the lambda's parameter, does it create multiple instances of the lambdas? I am not entirely sure how (Code 1) differs from (Code 2) and how (Code 2) differs from (Code 3). I was expecting (Code 3) to create multiple instances of the lambda so the output would be (Joe 1, Joe 2, 1.5 1) but turns out to be (Joe 1, Joe 2, 1.5 3).

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Goddrew
  • 125
  • 2
  • 5

6 Answers6

3

Having auto for one of the lambda's parameter, does it create multiple instances of the lambdas?

It's not creating multiple instances of lambda, but the lambda would have a templated operator(), and multiple instantiations with different types.

For the 1st case, when being called twice with const char*, the same static variable l (defined in instantiation of operator() for type const char*) is printed out. When being called with double, the static variable l (defined in instantiation of operator() for type double) is printed, so you'll get

Joe 1
Joo 2
1.5 1

For the 2nd case, they all refer to the same variable l defined out of the lambda, then you'll get

Joe 1
Joo 2
1.5 3

For the 3rd case, they all refer to the same variable l captured by the lambda, (even different instantiations of operator() get called,) then you'll get

Joe 1
Joo 2
1.5 3
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
2

In the first sample there are as many static variables as there are instantiations of the lambda. And there could be many of them because you specify the parameter as auto, that makes your lambda some sort of a template.

The third code captures l by value each time the lambda is created, and creates a local copy of it, so nothing is shared at all.

Let's see an example. Let's create a function f() with the lambda defined inside, and call the function twice:

void f() {
    //auto l1 = ...

    l1(1);
    l1(2);
    l1(3);
    l1('a');
    l1('b');
    l1('c');
}

f();
std::cout << "---" << std::endl;
f();

This code calls the same lambda 3 times with the int value, and 3 times with the char.

Code 1: l is shared between the instantiations, so we would see 2 different variables used:

    1 1
    2 2
    3 3
    a 1
    b 2
    c 3
    ---
    1 4
    2 5
    3 6
    a 4
    b 5
    c 6

The second call to f() allows you to reuse the static variable, but there are two different variables for two distinct types.

Code 2: l is the only static variable and is shared between all lambdas:

    1 1
    2 2
    3 3
    a 4
    b 5
    c 6
    ---
    1 7
    2 8
    3 9
    a 10
    b 11
    c 12

Code 3: you have one lambda created per each function invocation, and this lambda uses the only instance of the variable l that is captured on the time of creation:

    1 1
    2 2
    3 3
    a 4
    b 5
    c 6
    ---
    1 1
    2 2
    3 3
    a 4
    b 5
    c 6

If you run this code again, you would see that the l is recreated again, and the result would be repeated.

Dmitry Kuzminov
  • 6,180
  • 6
  • 18
  • 40
1

For this version:

auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };

there are 2 instantiations of the lambda's call operator, one for the 2 const char * calls, and one for the double call. There is one static variable l per instantiation, and so the output is:

l1("Joe"); // Joe 1
l1("Joo"); // Joo 2
l1(1.5);   // 1.5 1

For this version:

static int l = 0;
auto l1 = [](auto a) {  std::cout << a << " " << ++l << std::endl; };

there is exactly one static variable l, so there is only 1 l for all instantiations of the lambda's call operator, and the output is:

l1("Joe"); // Joe 1
l1("Joo"); // Joo 2
l1(1.5);   // 1.5 3

The 3rd version has the same effect as the 2nd except that the variable l needs to be declared in a function local scope.

cigien
  • 57,834
  • 11
  • 73
  • 112
1

Lambda's are basically just an easy way of declaring functors. This code:

#include <iostream>

auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };

static int m = 0;
auto l2 = [](auto a) {  std::cout << a << " " << ++m << std::endl; };

int n = 0;
auto l3 = [n](auto a) mutable {  std::cout << a << " " << ++n << std::endl; };

int main(){
    l1("Joe");
    l1("Joo");
    l1(1.5);
    std::cout << "\n";
    l2("Joe");
    l2("Joo");
    l2(1.5);
    std::cout << "\n";
    l3("Joe");
    l3("Joo");
    l3(1.5);
}

Is basically equivalent to:

#include <iostream>
struct F1
{
    template < typename T >
    void operator ()(T a)
    {
        static int l = 0; std::cout << a << " " << ++l << std::endl;;
    }
};
auto l1 = F1{};

static int m = 0;
struct F2
{

    template < typename T >
    void operator ()(T a)
    {
        std::cout << a << " " << ++m << std::endl;
    }
};
auto l2 = F2{};

struct F3
{
    F3(int n)
     :n(n)
    {
    }

    template < typename T >
    void operator ()(T a)
    {
        std::cout << a << " " << ++n << std::endl;
    }
    int n;
};
int n = 0;
auto l3 = F3{n};

int main(){
    l1("Joe");
    l1("Joo");
    l1(1.5);
    std::cout << "\n";
    l2("Joe");
    l2("Joo");
    l2(1.5);
    std::cout << "\n";
    l3("Joe");
    l3("Joo");
    l3(1.5);
}

In F1 each instantiation of operator () with a different type gets a different instance of the l variable so you get the result 1, 2, 1.

In F2 all instantiations of operator () share the same static global variable so you get the result 1, 2, 3.

In F3 all instantiations of operator () share the same member (capture in the case of the lambda) variable so you get the result 1, 2, 3.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
0

Code 1 vs Code 2

The difference between code 1 and 2 is simply instantiating the variable l inside vs. outside of th lambda. There is no guarantee that code 2 will see l and you definitely should not be updating l since it isnt captured by the lambda.

Code 2 vs Code 3

The difference here is that you have captured l [l] which means the lambda has access to that variable within its scope. You have also defined the lambda as mutable which means you can update the variables that have been captured (l in this case).

Code 3 is the correct version. Since you are trying to access and update variable l , you need to capture it [l] and define the lambda as mutable so you can update it.

Grant Singleton
  • 1,611
  • 6
  • 17
0

Code 1

auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };
    l1("Joe");
    l1("Joo");
    l1(1.5);

Implementation of lambda

class _tmp_lambda
{
 public:
 _tmp_lambda() {}
 void operator()(const char * a) const
 {
         static int l = 0;
         std::cout << a << " " << ++l << std::endl;
 }
 void operator()(double a) const
 {
         static int l = 0;
         std::cout << a << " " << ++l << std::endl;
 }
};

Code 2

static int l = 0;
auto l1 = [](auto a) {  std::cout << a << " " << ++l << std::endl; };

Implementation of lambda

class _tmp_lambda
private:
static int l = 0; /// <--
public: 
{
 _tmp_lambda() {}
 void operator()(const char * a) const
 {
         std::cout << a << " " << ++l << std::endl;
 }
 void operator()(double a) const
 {
         std::cout << a << " " << ++l << std::endl;
 }
};

Code 3

int l = 0;
auto l1 = [l](auto a) mutable {  std::cout << a << " " << ++l << std::endl; };

Implementation of lambda

class _tmp_lambda
private:
int l = 0; /// <--
public: 
{
 _tmp_lambda(int _l):l(_l) {}
 void operator()(const char * a) // Not a const 
 {
         std::cout << a << " " << ++l << std::endl;
 }
 void operator()(double a) // Not a const
 {
         std::cout << a << " " << ++l << std::endl;
 }
};

Above implementation can explain you all your outputs.

SaurabhS
  • 633
  • 1
  • 7
  • 18