1

When std::function is created with a lambda, the std::function internally makes a copy of the lambda object. Thus, our call to fn() is actually being executed on the copy of our lambda, not the actual lambda.

According to the statements above, what is the point of passing a lambda by 'reference using &' or passing by 'value' in the code below while the std::function always makes a copy of the lambda object?

In other words, in the code below, which part of lambda will OR will not be modified/effected when the parameter of 'invoke' function is passed by reference or value?

#include <iostream>
#include <functional>
 
void invoke(const std::function<void(void)>& fn) // # PASS LAMBDA BY REFERENCE*********************
 
{
    fn();
}
 
int main()
{
    int i{ 0 };
 
    // Increments and prints its local copy of @i.
    auto count{ [i]() mutable {
      std::cout << ++i << '\n';
    } };
 
    invoke(count);
    invoke(count);
    invoke(count);
 
    return 0;
}

#include <iostream>
#include <functional>
 
void invoke(const std::function<void(void)> fn) // # PASS LAMBDA BY VALUE*********************
{
    fn();
}
 
int main()
{
    int i{ 0 };
 
    // Increments and prints its local copy of @i.
    auto count{ [i]() mutable {
      std::cout << ++i << '\n';
    } };
 
    invoke(count);
    invoke(count);
    invoke(count);
 
    return 0;
Sami
  • 513
  • 4
  • 11
  • 3
    lambda is not `std::function`... – Jarod42 Jul 23 '20 at 13:58
  • 1
    In both cases, you create a temporary `std::function` (by call) from a lambda... – Jarod42 Jul 23 '20 at 14:01
  • Then what is the point of using & operator? and what does "(by call)" mean? – Sami Jul 23 '20 at 14:02
  • 1
    In this case you'd be better off using `template void invoke(const Func &func);` which will avoid converting the `lambda` to a `std::function`, thus increasing overhead. – Mirco De Zorzi Jul 23 '20 at 14:04
  • It is similar to `void foo(const std::string&)` vs `void foo(std::string)` whereas you pass `const char*` – Jarod42 Jul 23 '20 at 14:04
  • @Jarod42 First I thought that way. But when I tried to print the address of the lambda count in the 'main' function (&count) and inside the 'invoke' function (&fn), I got two different addresses while with your example we get the same address. Then, I found that this & operator seems to be working differently in this context! – Sami Jul 23 '20 at 14:09
  • @ Mirco De Zorzi : Thanks for your suggestion. I am kind of beginner to C++ and I haven't yet studied "templates". I am reading all concepts step by step :) – Sami Jul 23 '20 at 14:12

1 Answers1

1

You are mixing two things - passing by reference and casting.

Passing by reference

This is roughly how lambdas are implemented:

struct Lambda{
    //Capture by value
    Lambda(int i):_i(i){}

    void operator()() const{
        std::cout << ++_i << '\n';
    }
    mutable int _i;
};
//BTW const is somewhat redundant
void invoke1(const Lambda fn) // # PASS LAMBDA BY VALUE
{
    fn();
}
//Const is important, but not yet.
void invoke2(const Lambda& fn) // # PASS LAMBDA BY REFERENCE
{
    fn();
}
 
int main()
{
    int i{ 0 };
 
    // Increments and prints its local copy of @i.
    Lambda count{i};
 
    invoke1(count);//1
    invoke1(count);//1
    invoke1(count);//1

    invoke2(count);//1
    invoke2(count);//2
    invoke2(count);//3
 
    return 0;
}

The code produces the output you probably wanted. Note that the mutable usage is completely incorrect, but I made it this way so the code compiles. See link for how std::function does this.

Casting

As @Jarod42 wrote, lambda is not a std::function, but it is implicitly convertable to one via std::function's constructor. This is not how std::function is implemented at all, there is much more magic involved in order to allow capturing any callable. But it illustrates the point.

#include <iostream>
#include <functional>
 
struct Lambda{
    //Capture by value
    Lambda(int i):_i(i){}

    void operator()() const{
        std::cout << ++_i << '\n';
    }
    mutable int _i;
};

struct Function{
    //Makes a copy.
    Function(Lambda lambda):_lambda(std::move(lambda)){}

    void operator()() const{
        _lambda();
    }

    Lambda _lambda;
};


void invoke1(const Function fn) // # PASS FUNCTION BY VALUE
{
    fn();
}
//Const is very important.
void invoke2(const Function& fn) // # PASS FUNCTION BY REFERENCE
{
    fn();
}
 
int main()
{
    int i{ 0 };
 
    // Increments and prints its local copy of @i.
    Lambda count{i};
 
    invoke1(count);//1
    invoke1(count);//1
    invoke1(count);//1

    invoke2(count);//1
    invoke2(count);//1
    invoke2(count);//1
    
 
    return 0;
}

All invoke2 calls still print 1 because on each call a new Function object is created which creates its own local copy of count lambda object. Same happened, of course, for invoke1 calls too but it makes no difference as the copy would be made either way.

Note that the const in invoke2(const Function&) is very improtant, without it the calls would result in compile errors. This is because const Function& allows the parameter to bind both to L-values and R-values(~temporaries).

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • Thank you so much for your time and explanations.I still don't get what the difference was. Would you please introduce me any books or resources to get to know what is happening behind the scenes of a std::function? Thank you – Sami Jul 26 '20 at 01:26
  • @sami This has almost nothing to do with `std::function`. I'm not sure what exactly is not clear to you, the difference is that passing by value creates a copy, passing by reference does not. BUT type conversion creates a copy no matter how you pass. Your problem is that you are passing a lambda function but neither function accepts a lamba, only `std::function` objects. You can search for "how is `std::function` implemented" but it won't answer your question. You need to look up language rules on parameter passing **and** casting. – Quimby Jul 26 '20 at 12:41