1

I am not sure how to explain this behaviour, displayed here with a minimal example. Why isn't size correctly captured ?

#include <iostream>
#include <vector>
using namespace std;


auto&& matcher1KO = [] (vector<int> &v){ 
    int size = v.size();
    cout << "size outside : " << size << "\n";  // print 1
    return [&] (bool b) {
      cout << "v.size() : " << v.size() << "\n"; // print 1
      cout << "size inside : " << size << "\n"; // print 0
    };
};

auto&& matcher2OK = [] (vector<int> &v){ 
    int size = v.size();
    cout << "size outside : " << size << "\n"; // print 1
    return [&] () {
      cout << "v.size() : " << v.size() << "\n"; // print 1
      cout << "size inside : " << size << "\n"; // print 1
    };
};

int main() {
  vector<int> v {+1};

  auto matcherf1 = matcher1KO(v); // 
  matcherf1(true);

  auto matcherf2 = matcher2OK(v);
  matcherf2();
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
nicolas
  • 9,549
  • 3
  • 39
  • 83
  • https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice – kesarling He-Him Apr 27 '20 at 10:23
  • 2
    You return a lambda that captured the local variable `size` by reference, and said reference becomes dangling. You need to capture by value. – Quentin Apr 27 '20 at 10:24
  • 1
    `auto&& matcher1KO` Why the `&&` here? – Asteroids With Wings Apr 27 '20 at 10:39
  • 2
    You return lambdas which are local by `auto&&`, which is also undefined behaviour. Lifetime of these closures are not prolonged by binding it to reference in return type. – rafix07 Apr 27 '20 at 10:40
  • 1
    @rafix07 You mean `matcher1KO` and `matcher2OK` are dangled? Don't they bind to the lambda whose lifetime get extended? And the lambda they bind to return by value. Sorry I can't see UB for it... – songyuanyao Apr 27 '20 at 10:51
  • 2
    @songyuanyao You are right, i misread these lines. The outermost lambda is prvalue, then it is just bound to rvalue reference as global variable `matcher1KO`, so everthing works fine here. I retract my previous comment. – rafix07 Apr 27 '20 at 10:59

1 Answers1

2

Both the code have undefined behavior, anything is possible.

The reason is the same for the two cases: the variable size is a local object inside the operator() of the lambda, it will be destroyed when the invocation ends. You're capturing size by-reference and the reference is dangled.

Changing it to capture-by-value would be fine. e.g.

return [=] (bool b) {
  cout << "v.size() : " << v.size() << "\n"; // print 1
  cout << "size inside : " << size << "\n"; // print 1
};
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • I see. Why would the compiler not catch "undefined behavior" ? This is really surprising – nicolas Apr 27 '20 at 10:27
  • @nicolas UB means compilers could do anything they want; this is programmers' responsibility. – songyuanyao Apr 27 '20 at 10:30
  • @nicolas The whole reason for the "existence" of undefined behaviour in the language, is that it covers those things that (in general) would be too difficult, or not worth the time, for the compiler to diagnose. It is the programmer's responsibility to get those right. Dangling references are a textbook case of that. – Asteroids With Wings Apr 27 '20 at 10:40
  • This UB couldn't mean anything useful. so there is no point in not ruling *this* UB out. some others UB could mean something too complex to express or check for for compiler. this is not the case. – nicolas Apr 27 '20 at 10:42
  • 1
    @nicolas [Clang](https://wandbox.org/permlink/2uPi9P3IdnFv2vLA) gives warning for this. – songyuanyao Apr 27 '20 at 10:47
  • _Why would the compiler not catch "undefined behavior" ?_ My compiler caught it. Do you have your compiler's warnings enabled? – Eljay Apr 27 '20 at 11:41