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;
7
  • 3
    lambda is not std::function... Commented Jul 23, 2020 at 13:58
  • 1
    In both cases, you create a temporary std::function (by call) from a lambda... Commented Jul 23, 2020 at 14:01
  • Then what is the point of using & operator? and what does "(by call)" mean? Commented Jul 23, 2020 at 14:02
  • 1
    In this case you'd be better off using template <typename Func> void invoke(const Func &func); which will avoid converting the lambda to a std::function, thus increasing overhead. Commented Jul 23, 2020 at 14:04
  • It is similar to void foo(const std::string&) vs void foo(std::string) whereas you pass const char* Commented Jul 23, 2020 at 14:04

1 Answer 1

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).

Sign up to request clarification or add additional context in comments.

2 Comments

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 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.