10

For a lambda, I'd like to capture something by reference which was held in the outer scope by reference already. Assume that the referenced value outlives the lambda, but not the scope in which the lamdba gets created.

I know that if a lambda captures a reference variable by value, the referenced object will be copied. I'd like to avoid this copy.

But what happens if I capture a reference variable by reference? What if the original reference variable will get out of scope before executing the lambda? Is this safe? In other words: is the object behind the reference referenced or is the reference variable referenced in the lambda?

auto f() {
    const auto & myRef = g();
    return [&]{ myRef.doSomething(); };
}

f()();  // Safe?
Community
  • 1
  • 1
leemes
  • 44,967
  • 21
  • 135
  • 183
  • I believe not. Beyond theoretical, one efficient way to implememt `[&]` is to store the stack depth, and hardcode offsets from this in the lambda body. This reduces your state to one pointer, instead of one per variable. As to this being a legal implementation, I think I saw the argument elsewhere on SO. Oh, and note reference lifetime extension dangers. As a solution, capture a referemce wrapper by value? – Yakk - Adam Nevraumont Apr 14 '14 at 18:17
  • 3
    Dup? http://stackoverflow.com/q/21443023/420683 – dyp Apr 14 '14 at 18:18
  • @dyp Oh, didn't see that when writing the question. Now I feel bad. But thanks. :) – leemes Apr 14 '14 at 18:26
  • With c++1y closure, the problem is solve : `auto f() { return [&myRef = g()]{ myRef.doSomething(); }; }`. – galop1n Apr 14 '14 at 18:29
  • Ok if we talk about C++1y: Assuming that I want to separate `myRef = g()` and the lambda (and therefore don't want to write `[myRef = g()]` in the lambda), can I "re-reference" the object with an expression like `*&myRef` in the capture expression? Something like `[myRef = *&myRef]` – leemes Apr 14 '14 at 18:35
  • 1
    @dyp, was assuming that g returns a reference to a long term object. As explained in the OP. – galop1n Apr 14 '14 at 18:38
  • 2
    Another workaround: `auto myRef = std::cref(g()); return [=]{ myRef.get().doSomething(); }` – aschepler Apr 14 '14 at 19:05
  • @galop1n Ah, sorry, I misread the OP. – dyp Apr 14 '14 at 19:07
  • 1
    @leemes `[&myRef = myRef]` should be fine. This creates a reference data member in the closure. `[myRef = *&myRef]` performs a copy, just like `[myRef = myRef]`. – dyp Apr 14 '14 at 19:12
  • @aschepler Nice! I'll use that. – leemes Apr 14 '14 at 19:26
  • @dyp Ah okay, thank you for that. I won't use it since my project should stay C++11 compatible but it's good to know how those things will work in C++1y. – leemes Apr 14 '14 at 19:26
  • @galop1n The question dyp linked as duplicate also captures a 'long-term' object (it survives until the end of main), so there is still no difference between these questions. – TamaMcGlinn Jan 26 '21 at 08:02
  • Does this answer your question? [Capturing a reference by reference in a C++11 lambda](https://stackoverflow.com/questions/21443023/capturing-a-reference-by-reference-in-a-c11-lambda) – TamaMcGlinn Jan 26 '21 at 10:49

1 Answers1

5

Yes, the key issue in capturing an object by reference is the lifetime of the referenced object, not the lifetime of any intervening references used to get it. You can think of a reference as an alias rather than an actual variable. (And in the type system, references are treated differently from regular variables.) The reference aliases the original object, and is independent of other aliases used to alias the object (other than the fact that they alias the same object).

=====EDIT=====

According to the answer given to this SO question (pointed out by dyp), it appears that this may not be entirely clear. Throughout the rest of the language, the concept of a "reference to a reference" doesn't make sense and a reference created from a reference becomes a peer of that reference, but apparently the standard is somewhat ambiguous about this case and lambda-captured references may in some sense be secondary, dependent on the stack frame from which they were captured. (The explicit verbiage the SO answer quotes specifically calls out the referenced entity, which would on the face indicate that this usage is safe as long as the original object lives, but the binding mechanism may implicate the capture chain as being significant.)

I would hope that this is clarified in C++14/17, and I would prefer it to be clarified to guarantee legality for this usage. In particular, I think the C++14/17 ability to capture a variable via an expression will make it more difficult to simply capture a scope via a stack-frame pointer and the most sensible capture mechanism would be to generally capture the specific entities individually. (Perhaps a stack-frame-capture could be permitted if an actual local object is captured by reference, since this would result in UB if the lambda is called outside the scope in any event.)

Until we get some clarification, this may not be portable.

Community
  • 1
  • 1
Adam H. Peterson
  • 4,511
  • 20
  • 28
  • Thank you, I was 99% sure, but wanted some confirmation. – leemes Apr 14 '14 at 18:14
  • Capturing by reference does not (necessarily) create a reference. – dyp Apr 14 '14 at 18:21
  • According to Yakk's comment to my question, your answer is not correct. :( – leemes Apr 14 '14 at 18:28
  • Except the "referenced entity" in this case is the function-local reference `myRef`, not an object. – aschepler Apr 14 '14 at 19:16
  • @aschepler, no, the "referenced entity" can't be a reference. The C++ language does not have the concept of a reference-to-a-reference; they always collapse into a single reference to the original object. The only reason there is uncertainty here is because the standard is unclear about the allowable mechanics of the capture mechanism. – Adam H. Peterson Apr 14 '14 at 19:18
  • 4
    There is no reference in the lambda (not necessarily), hence there's no reference to reference. "Capture by reference" is a rather historical term, since an earlier version of lambdas specified a data member of reference type for the closure type. This has been removed prior to C++11, now only the *access to the entity* is specified inside the lambda (or rather, there's no transformation to another entity, so the original entity from the outside scope is accessed). And the entity here is a variable declared as a reference. – dyp Apr 14 '14 at 19:24
  • (I hope it's okay that I struck out your first paragraph since it is incorrect. I accepted your answer because of your edit.) – leemes Apr 14 '14 at 19:45