Why does a variable refer to the variable outside of a lambda, if it is not odr used?
Asked Answered
A

1

10

Look at this example (godbolt):

void foo(int &par) {
    auto l = [par]() {
        decltype(par) x;
    };
}

This program does not compile, because the par in decltype(par) refers to the parameter in foo, and not to the variable of the lambda (so decltype(par) is actually a reference type, which needs to be initialized).

Here is a relevant quote from the standard of C++14 (sorry about quoting an old standard, but I think it's easier to understand).

expr.prim.lambda/18 (emphasis is mine):

"Every id-expression within the compound-statement of a lambda-expression that is an odr-use of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [ Note: An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. Furthermore, such an id-expression does not cause the implicit capture of the entity. — end note ]"

And the standard makes sure that if decltype used on a non-id-expression, then even tough it doesn't odr-use the variable, the captured one is used (expr.prim.lambda/19):

"Every occurrence of decltype((x)) where x is a possibly parenthesized id-expression that names an entity of automatic storage duration is treated as if x were transformed into an access to a corresponding data member of the closure type that would have been declared if x were an odr-use of the denoted entity."

So, depending on these rules (and maybe the standard has other related rules), a variable in a lambda may refer to the captured one, or may refer to the original one in the enclosing function.

I have two questions:

  1. Why is it designed this way? Why not always use the captured variable? What does this extra complexity give us?
  2. How can I acquire the type of a captured variable inside a lambda?

(Note: a related previous question: What is the type of a by-value lambda capture of a reference?)

Avast answered 5/9, 2023 at 9:37 Comment(14)
"Why is it designed this way? Why not always use the captured variable? What does this extra complexity give us?" - Writing it in a capture list, and actually having be captured need not be the same thing. If it's not ODR-used, the lambda may not need to have a member for that capture at all (valid optimization). But in that case, it becomes problematic to discuss the type of something that might not be there. Maybe I'm pessimistic, but if it resolved to a "would be entity" instead of an actual one, that could be problematic?Absence
C++23 at least would allow you to do decltype(auto(par)) which is what most of us wanted to have for 10+ years, I think.Absence
User @BrianBi has already explained all the details in the question Regarding decltype() for captured entities, which compiler is ISO compliant? which this one duplicates. It also explains what paper the new wording originates from etc. If this question isn't a dupe, then it should explain how Brian's answer (which goes deep into all the technicalities) doesn't cover the topic yet.Inflated
@JanSchultke OP displayed perfect understanding of standard and did not ask why his code does not compile ... but why it is designed in that manner and how he can find out the decltype of captured variable inside lambda.Musset
@ÖöTiib the question which I've linked is also a language-lawyer question about language technicalities. The author showed understanding of the standard, and I believe that the linked question thoroughly answers this one.Inflated
See also See also P2036R3 Change scope of lambda trailing-return-type (made wording changes to some relevant sections and examples).Inflated
@StoryTeller-UnslanderMonica decltype(auto(x)) is always an object, even if x is captured by reference. I suspect there is no way to get the type of the captured entity, and just 2. would be a good question on its own.Inflated
@JanSchultke: I don't think that my question is a duplicate of that. C++11 has the same wording, so this behavior doesn't relate to the mentioned proposal. And I'd like to know the why, the design rationale of this behavior. And also how to circumvent this rule (i.e., how to get the type of the captured variable). I don't see answers to these.Avast
@JanSchultke - Yes, it's an object. The OP wants an object, like they'd get if par was odr-used and captured. So if they want an object, it's useful to tell them how to spell "an object".Absence
@StoryTeller-UnslanderMonica the problem is that if par was captured by reference, auto(par) is still going to be an object. It's not the type of the captured variable, and could just as well be written as std::remove_reference_t<decltype(par)>. I'm just trying to say that there should be a disclaimer that using auto is not a general solution.Inflated
@JanSchultke - It's only a problem if the OP doesn't want an object, which they do. Or it's a problem if a reader is being intentionally obtuse, which is also common in langague-lawyer questions, I suppose.Absence
@StoryTeller-UnslanderMonica OP asked "How can I acquire the type of a captured variable inside a lambda?", and decltype(auto(x)) does not adequately solve that. It doesn't get the type of the lambda member generally, just of the one in the example. Simply writing int would also be a viable solution to that problem.Inflated
@StoryTeller-UnslanderMonica: "it becomes problematic to discuss the type of something that might not be there". Why? I don't see any problems with this. Just because the compiler optimizes away a captured variable, it still knows the type of that variable. If I define a int x; local variable, and then don't odr use it, I can still say decltype(x), and it will be int.Avast
Declarations seem different. Even if optimized away in translation, x is still "there" in the abstract machine. In the case of a lambda capture, it may not exist in the abstract machine at all, to my reading. I don't know why that is, though. I can't really answer why it's like that, only observe it.Absence
P
0
  1. There are various ways to design with those dozen different explicit and implicit captures. Other ways may be similarly confusing or more wasteful. You are transforming a reference into copy with same name so it can happen to be confusing on its own.

  2. Can do by-copy capture with an initialiser:

    void foo(int &par) {
       auto l = [cap = par]() {
           decltype(cap) x; // no confusion about names
       };
    }
    
Pleiades answered 5/9, 2023 at 10:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.