Question

Reading the answer from Capturing a reference by reference in a C++11 lambda makes me think that the following code generates undefined behavior because of the ended lifetime of i in the lambda-capture. Is that right for C++1y? I am asking because g++ 4.8.2 translates the code just fine.

#include <iostream>

auto captureFct( ) {
    int i=0;
    auto set = [&i](int _i){ i=_i; };
    auto get = [&i](){ return i; };
    return std::pair<decltype(set),decltype(get)>(set,get);
}


int main() {
    auto myPair = captureFct();
    auto set1 = myPair.first;
    auto get1 = myPair.second;

    auto myPair1 = captureFct();
    auto set2 = myPair1.first;
    auto get2 = myPair1.second;

    std::cout << "\nget1:" << get1() << " get2:" << get2() << '\n';
    set1(1); set2(2);
    std::cout << "\nget1:" << get1() << " get2:" << get2();
}

/*
    Local Variables:
    compile-command: "g++ -std=c++1y lambda.cc -o a.exe && ./a.exe"
    End:
 */

The output is interesting:

get1:0 get2:0

get1:2 get2:2

It seems that the same reference is used for all lambdas.

This behavior differs from the behavior of the following elisp code (as close to the c++ code as possible):

(defun captureFct ()
  (lexical-let ((i 0))
    (list :set (lambda (_i) (setq i _i))
      :get (lambda () i))))

(setq myPair (captureFct))
(setq myPair1 (captureFct))

(message "\nget1: %d get2: %d"
     (funcall (plist-get myPair :get))
     (funcall (plist-get myPair1 :get)))

(funcall (plist-get myPair :set) 1)
(funcall (plist-get myPair1 :set) 2)

(message "\nget1: %d get2: %d"
     (funcall (plist-get myPair :get))
     (funcall (plist-get myPair1 :get)))

The output of the elisp code is:

get1: 0 get2: 0

get1: 1 get2: 2

I think I know already the answer. But, I post this question anyway since it is interesting for folks that do both elisp and c++.

Last but not least a C++ version that works like the elisp version:

#include <iostream>
#include <memory>

auto captureFct( ) {
    std::shared_ptr<int> pi(new int(0));
    auto set = [pi](int _i){ *pi=_i; };
    auto get = [pi](){ return *pi; };
    return std::pair<decltype(set),decltype(get)>(set,get);
}


int main() {

    auto myPair = captureFct();
    auto set1 = myPair.first;
    auto get1 = myPair.second;

    auto myPair1 = captureFct();
    auto set2 = myPair1.first;
    auto get2 = myPair1.second;



    std::cout << "\nget1:" << get1() << " get2:" << get2() << '\n';
    set1(1); set2(2);
    std::cout << "\nget1:" << get1() << " get2:" << get2();
}

/*
    Local Variables:
    compile-command: "g++ -std=c++1y lambda.cc -o a.exe && ./a.exe"
    End:
 */
Was it helpful?

Solution

Yes, it's undefined behaviour, since the references inside the lambdas become dangling as soon as captureFunc() exits (*).

What's probably happening in your case is that the references (which are just pointers under the hood) still point to the space on the stack where i was on the first invocation of captureFunc(), and it ends up in exactly the same location on the second invocation of captureFunc(); so the net effect is that all of get1, get2, set1, set2 have their internal i reference pointed to the same (currently unused) location in memory, so they modify it for each other.

Of course, the above is just speculation, and could change next time you (or run) the program, since Undefined Behaviour is Undefined.

(*) Quoting C++11, [expr.prim.lambda]§22:

[ Note: If an entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. —end note ]

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top