Lambda implicit capture fails with variable declared from structured binding

32.2k views Asked by At

With the following code, I get a compile error C2065 'a': undeclared identifier (using visual studio 2017):

[] {
    auto [a, b] = [] {return std::make_tuple(1, 2); }();
    auto r = [&] {return a; }(); //error C2065
}();

However, the following code compiles:

[] {
    int a, b;
    std::tie(a, b) = [] {return std::make_tuple(1, 2); }();
    auto r = [&] {return a; }();
}();

I thought that the two samples were equivalent. Is it a compiler bug or am I missing something ?

4

There are 4 answers

14
T.C. On BEST ANSWER

Core issue 2313 changed the standard so that structured bindings are never names of variables, making them never capturable.

P0588R1's reformulation of lambda capture wording makes this prohibition explicit:

If a lambda-expression [...] captures a structured binding (explicitly or implicitly), the program is ill-formed.

Note that this wording is supposedly a placeholder while the committee figures out exactly how such captures should work.

Previous answer kept for historical reasons:


This technically should compile, but there's a bug in the standard here.

The standard says that lambdas can only capture variables. And it says that a non-tuple-like structured binding declaration doesn't introduce variables. It introduces names, but those names aren't names of variables.

A tuple-like structured binding declaration, on the other hand, does introduce variables. a and b in auto [a, b] = std::make_tuple(1, 2); are actual reference-typed variables. So they can be captured by a lambda.

Obviously this is not a sane state of affairs, and the committee knows this, so a fix should be forthcoming (though there appears be some disagreement over exactly how capturing a structured binding should work).

0
fdermishin On

You can use init-capture like this, as suggested in https://burnicki.pl/en/2021/04/19/capture-structured-bindings.html. The variable is captured by reference, so there is no overhead, and no need to deal with pointers.

auto [a, b] = [] { return std::make_tuple(1, 2); }();
auto r = [&a = a] { return a; }();

Using the same name both for structured binding and for its reference can be misleading, but actually it means

auto r = [&a_ref = a] { return a_ref; }();

As far as I know, it works on all compilers starting from C++17 and above, including clang.

4
perimasu On

A possible workaround is to use a lambda capture with the initializer. The following code compiles fine in Visual Studio 2017 15.5.

[] {
    auto[a, b] = [] {return std::make_tuple(1, 2); }();
    auto r = [a = a] {return a; }();
}();
6
Kai Gu On

Now lambda could capture structured binding since c++20, see this.