Copying closure of lamda expression

Hello,

If you think this is a beginners question feel free to beat me for not using the correct forum :)

Consider the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <functional>

using namespace std;
class A {
    public: 
    A() {cout << "A created\n";}
    A(const A&) {cout << "A copied\n";}
    ~A() {cout << "A deleted\n";}
};

ostream& operator<< (ostream &s,  A const & a) {
    return s << "A";
}

void f( function <void()> const &g)
{
    g();
}

class Lam {
    public:
    A _a;
    void operator () () const { cout << "Lam called " << _a << "\n";}
    Lam (const A & a ) : _a(a) { cout << "Lam created\n";}
    ~Lam() {cout << "Lam deleted\n";}
};

void f (Lam const &g)
{
    g();
}

int main()
{
    A a;
    cout<<"Declaring g\n";
    auto g = [=] { cout << "Lambda called " << a  << "\n";};
    cout << "Calling g()\n";
    g();
    cout << "Calling f(g)\n";
    f(g);
    cout << "Calling Lambda directly\n";
    f( [=] { cout << "Lambda called " << a << "\n";});
    cout << "Declaring Lam\n";
    Lam l(a);
    cout << "Calling f(l)\n";
    f(l);
    cout << "Finished\n";

    return 0;
}

Output is:

A created                                                                    
Declaring g                                                                  
A copied                                                                     
Calling g()                                                                  
Lambda called A                                                              
Calling f(g)                                                                 
A copied                                                                     
A copied                                                                     
Lambda called A                                                              
A deleted                                                                    
A deleted                                                                    
Calling Lambda directly                                                      
A copied                                                                     
A copied                                                                     
Lambda called A                                                              
A deleted                                                                    
A deleted                                                                    
Declaring Lam                                                                
A copied                                                                     
Lam created                                                                  
Calling f(l)                                                                 
Lam called A                                                                 
Finished                                                                     
Lam deleted                                                                  
A deleted                                                                    
A deleted                                                                    
A deleted 

Can anybody explain, why there are two copy operations each time the lambda expression is used, I had expected the same behavior as using the Lam class.
Last edited on
feel free to beat me for not using the correct forum

You didn’t use [ code ][ / code ] tags either.

each time the lambda expression is used

Did you mean when “f()” is invoked?
g() itself copies ‘A’ just once, because you asked to capture by copy and not by reference.

Please compare:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <functional>


class A { 
public:
    A() { std::cout << " <A_created> "; }
    A(const A&) { std::cout << " <A_copied> "; }
    ~A() { std::cout << " <A_deleted> "; }
};


std::ostream& operator<< (std::ostream &s, [[maybe_unused]] const A& a)
{
    return s << 'A';
}


void f(const std::function <void()>& my_f)
{
    my_f();
}


int main()
{ 
    A a;

    std::cout << "\nDeclaring g: ";
    auto g { [a] {
        std::cout << "\"g() prints " << a << '\"';
    } };

    std::cout << "\nCalling g(): ";
    g();
    std::cout << "\nCalling f(g): ";
    f(g);
    std::cout << "\nCalling a lambda directly: ";
    f( [a]() {  std::cout << "\"anonymous lambda prints " << a << '\"'; } );
    std::cout << "\n\n";
    return 0;
}


Output:
 <A_created>
Declaring g:  <A_copied>
Calling g(): "g() prints A"
Calling f(g):  <A_copied>  <A_copied> "g() prints A"
<A_deleted>
<A_deleted>
Calling a lambda directly:  <A_copied>  <A_copied> "anonymous lambda prints A"
<A_deleted>
<A_deleted>
<A_deleted>
<A_deleted>


- - -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <functional>


class A { 
public:
    A() { std::cout << " <A_created> "; }
    A(const A&) { std::cout << " <A_copied> "; }
    ~A() { std::cout << "\n<A_deleted>"; }
};


std::ostream& operator<< (std::ostream &s, [[maybe_unused]] const A& a)
{
    return s << 'A';
}


void f(const std::function <void()>& my_f)
{
    my_f();
}


int main()
{ 
    A a;

    std::cout << "\nDeclaring g: ";
    auto g { [&a] {
        std::cout << "\"g() prints " << a << '\"';
    } };

    std::cout << "\nCalling g(): ";
    g();
    std::cout << "\nCalling f(g): ";
    f(g);
    std::cout << "\nCalling a lambda directly: ";
    f( [&a]() {  std::cout << "\"anonymous lambda prints " << a << '\"'; } );
    return 0;
}


 <A_created>
Declaring g:
Calling g(): "g() prints A"
Calling f(g): "g() prints A"
Calling a lambda directly: "anonymous lambda prints A"
<A_deleted>

If you want help, use code tags.
Sorry for not using code tags. I tried to insert them using the buttons in the web interface but they didn't show up. Unfortunately, I didn't know how to set them by hand and preview didn't work either.
Thank you Enoizat for providing a code tagged answer.

So back to my question:
Indeed my concern was the behavior when calling f(g): I wouldn't expect A to be copied at all, because it should be in the closure of g and g is passed by reference. So why twice?
Unfortunately, I didn't know how to set them by hand and preview didn't work either.


At the right hand top corner of the text box you place your question, code and other text in you will see a small menu of icons under the heading 'Format:'
So for future reference this is what you do:

1. Select your code using your mouse etc.
2. Press the <> icon.
3. Press the 'Submit' button at the bottom of the test box.

You can go back and do the same with your post by pressing the 'Edit' button :)
So why twice?

I think the problem is how the std::function instance is created.
I must admit I haven’t a good explanation :-) because I unsure of which constructor is picked between these:
https://en.cppreference.com/w/cpp/utility/functional/function/function

If the third is chosen, then I can read: “Copies … the target of other to the target of *this.”.
What I mean is (at least a temporary) instance of a std::function object is to be constructed, and that might require a further copy.

But we need to wait for some of those gurus who know nearly everything about how the standard library works — if you’re lucky, maybe some of them would come across this thread.

But you need to know they dislike reading code not included in proper tags ;-)
I think you’d better re-editing your first post as againtry suggested.

- - -
Additional observations (example of how to avoid building a std::function object):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <functional>


class A { 
public:
    A() { std::cout << " <A_created> "; }
    A(const A&) { std::cout << " <A_copied> "; }
    ~A() { std::cout << " <A_deleted> "; }
};


std::ostream& operator<< (std::ostream &s, [[maybe_unused]] const A& a)
{
    return s << 'A';
}


template <typename T> void f(const T& my_f)
{
    my_f();
}


int main()
{ 
    A a;

    std::cout << "\nDeclaring g: ";
    auto g { [a] {
        std::cout << "\"g() prints " << a << '\"';
    } };

    std::cout << "\nCalling g(): ";
    g();
    std::cout << "\nCalling f(g): ";
    f(g);
    std::cout << "\nCalling a lambda directly: ";
    f( [a]() {  std::cout << "\"anonymous lambda prints " << a << '\"'; } );
    std::cout << "\n\n";
    return 0;
}


Output:
 <A_created>
Declaring g:  <A_copied>
Calling g(): "g() prints A"
Calling f(g): "g() prints A"
Calling a lambda directly:  <A_copied> "anonymous lambda prints A" <A_deleted>

 <A_deleted>  <A_deleted>

So why twice?
The reason for this behavior is the = in [=] { ... };. See:

https://en.cppreference.com/w/cpp/language/lambda

= (implicitly capture the used automatic variables by copy).

With 'Calling f(g)' the lambda expression copies the automatic variable a. The lambda expression (already containing the copy of a) is again copied into the function object g which copies a the second time.

If you change it to & the output is probably more like you expect:
A created
Declaring g
Calling g()
Lambda called A
Calling f(g)
Lambda called A
Calling Lambda directly
Lambda called A
Declaring Lam
A copied
Lam created
Calling f(l)
Lam called A
Finished
Lam deleted
A deleted
A deleted
Last edited on
againtry wrote

At the right hand top corner of the text box you place your question, code and other text in you will see a small menu of icons under the heading 'Format:'
So for future reference this is what you do:

1. Select your code using your mouse etc.
2. Press the <> icon.
3. Press the 'Submit' button at the bottom of the test box.

You can go back and do the same with your post by pressing the 'Edit' button :)

I swear it didn't work that way, and it does not if I reedit. It only works for answer but not for a new topic and not by editing the first post. Very strange.
And the preview button does not work either. The buttons aren't even at the same location, they are below the textbox not beside.
But nevertheless I could insert the necessary tags by hand, now that I know the syntax.
I am aware that no copies are made if I pass the closure by reference (&) but I cannot understand why in the copy-by-value case the function object has to be copied. I am not even sure that it does. I tried to avoid it using the reference in the signature of f.
And why it does not copy, when f is declared as a template, totally beats me.
And why it does not copy, when f is declared as a template, totally beats me.

Because it doesn’t build a std::function object.
The compiler knows the type of g(), so the argument T& is replaced by the actual type of g().
but I cannot understand why in the copy-by-value case the function object has to be copied.
Actually the function object isn't copied. The copy happens during the conversion from the lambda expression to the function object. What exactly is done is an implementation detail.

And why it does not copy, when f is declared as a template, totally beats me.
Yes, no conversion, it is just passed through.
OK. Thank you who responded.
I was thinking, a lambda is a function object, but it only can be converted to a function object.
Topic archived. No new replies allowed.