3

I would like to know what my compiler does with the following code

void design_grid::design_valid()
{
    auto valid_idx = [this]() {
        if ((row_num < 0) || (col_num < 0))
        {
            return false;
        }
        if ((row_num >= this->num_rows) || (col_num >= this->num_rows))
        {
            return false;
        }
        return true;

    }

    /* some code that calls lambda function valid_idx() */
}

If I repeatedly call the class member function above (design_grid::design_valid), then what exactly happens when my program encounters the creation of valid_idx every time? Does the compiler inline the code where it is called later, at compile time, so that it does not actually do anything where the creation of valid_idx is encountered?

UPDATE

A segment of the assembly code is below. If this is a little too much too read, I will post another batch of code later which is coloured, to illustrate which parts are which. (don't have a nice way to colour code segments with me at the present moment). Also note that I have updated the definition of my member function and the lambda function above to reflect what it is really named in my code (and thus, in the assembly language).

In any case, it appears that the lambda is defined separately from the main function. The lambda function is represented by the _ZZN11design_grid12design_validEvENKUliiE_clEii function directly below. Directly below this function, in turn, the outer function (design_grid::design_valid), represented by _ZN11design_grid12design_validEv starts. Later in _ZN11design_grid12design_validEv, a call is made to _ZZN11design_grid12design_validEvENKUliiE_clEii. This line where the call is made looks like

call    _ZZN11design_grid12design_validEvENKUliiE_clEii #

Correct me if I'm wrong, but this means that the compiler defined the lambda as a normal function outside the design_valid function, then calls it as a normal function when it should? That is, it does not create a new object every time it encounters the statement which declares the lambda function? The only trace I could see of the lambda function in that particular location is in the line which is commented # tmp85, valid_idx.__this in the second function, right after the base and stack pointers readjusted at the start of the function, but this is just a simple movq operation.

.type   _ZZN11design_grid12design_validEvENKUliiE_clEii, @function
_ZZN11design_grid12design_validEvENKUliiE_clEii:
.LFB4029:
.cfi_startproc
pushq   %rbp    #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp  #,
.cfi_def_cfa_register 6
movq    %rdi, -8(%rbp)  # __closure, __closure
movl    %esi, -12(%rbp) # row_num, row_num
movl    %edx, -16(%rbp) # col_num, col_num
cmpl    $0, -12(%rbp)   #, row_num
js  .L107   #,
cmpl    $0, -16(%rbp)   #, col_num
jns .L108   #,
.L107:
movl    $0, %eax    #, D.81546
jmp .L109   #
.L108:
movq    -8(%rbp), %rax  # __closure, tmp65
movq    (%rax), %rax    # __closure_4(D)->__this, D.81547
movl    68(%rax), %eax  # _5->D.69795.num_rows, D.81548
cmpl    -12(%rbp), %eax # row_num, D.81548
jle .L110   #,
movq    -8(%rbp), %rax  # __closure, tmp66
movq    (%rax), %rax    # __closure_4(D)->__this, D.81547
movl    68(%rax), %eax  # _7->D.69795.num_rows, D.81548
cmpl    -16(%rbp), %eax # col_num, D.81548
jg  .L111   #,
.L110:
movl    $0, %eax    #, D.81546
jmp .L109   #
.L111:
movl    $1, %eax    #, D.81546
.L109:
popq    %rbp    #
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE4029:
.size   _ZZN11design_grid12design_validEvENKUliiE_clEii,.-_ZZN11design_grid12design_validEvENKUliiE_clEii
.align 2


.globl  _ZN11design_grid12design_validEv
.type   _ZN11design_grid12design_validEv, @function
_ZN11design_grid12design_validEv:
.LFB4028:
.cfi_startproc
pushq   %rbp    #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp  #,
.cfi_def_cfa_register 6
pushq   %rbx    #
subq    $72, %rsp   #,
.cfi_offset 3, -24
movq    %rdi, -72(%rbp) # this, this
movq    -72(%rbp), %rax # this, tmp85
movq    %rax, -32(%rbp) # tmp85, valid_idx.__this
movl    $0, -52(%rbp)   #, active_count
movl    $0, -48(%rbp)   #, row_num
jmp .L113   #
.L128:
movl    $0, -44(%rbp)   #, col_num
jmp .L114   #
.L127:
movl    -44(%rbp), %eax # col_num, tmp86
movslq  %eax, %rbx  # tmp86, D.81551
Konrad
  • 2,207
  • 4
  • 20
  • 31
  • 6
    Compile and check the assembly? – NathanOliver Oct 20 '16 at 11:57
  • 1
    https://godbolt.org/g/mKOiUc -- it's going to depend what you put in there. In this simple case, the whole thing is optimized out. – xaxxon Oct 20 '16 at 12:00
  • 2
    @KonradKapp Because it is immaterial to the question and no one is required to explain there votes. You can leave a comment if you want but it does not belong in the question. – NathanOliver Oct 20 '16 at 12:04
  • 1
    Have a look at this, nice session about lambdas from Jason Turner -> https://www.youtube.com/watch?v=_CbBfuQQQI8 – Dam Oct 20 '16 at 12:05
  • @NathanOliver I posted some assembly above, if you are interested – Konrad Oct 20 '16 at 13:29

2 Answers2

8

Closures (unnamed function objects) are to lambdas as objects are to classes. This means that a closure lambda_func is created repeatedly from the lambda:

[this]() {
        /* some code here */
    }

Just as an object could be created repeatedly from a class. Of course the compiler may optimize some steps away.

As for this part of the question:

Does the compiler inline the code where it is called later, at compile time, so that it does not actually do anything where the creation of lambda_func is encountered?

See:

Here is a sample program to test what might happen:

#include <iostream>
#include <random>
#include <algorithm>

class ClassA {
public:
    void repeatedly_called();

private:
    std::random_device rd{};
    std::mt19937 mt{rd()};
    std::uniform_int_distribution<> ud{0,10};
};


void ClassA::repeatedly_called()
{
    auto lambda_func = [this]() {
        /* some code here */
        return ud(mt);
    };

    /* some code that calls lambda_func() */
    std::cout << lambda_func()*lambda_func() << '\n';

};


int main()
{
    ClassA class_a{};
    for(size_t i{0}; i < 100; ++i) {
        class_a.repeatedly_called();
    }
    return 0;
}

It was tested here.

We can see that in this particular case the function repeatedly_called does not make a call to the lambda (which is generating the random numbers) as it has been inlined:

enter image description here enter image description here

In the question's update it appears that the lambda instructions were not inlined. Theoretically the closure is created and normally that would mean some memory allocation, but, the compiler may optimize and remove some steps.

With only the capture of this the lambda is similar to a member function.

Community
  • 1
  • 1
wally
  • 10,717
  • 5
  • 39
  • 72
6

Basically what happens is that the compiler creates an unnamed class with a function call operator, storing the captured variables in the class as member variables. Then the compiler uses this unnamed class to create an object, which is your lambda_func variable.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621