143

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 ?

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
ThreeStarProgrammer57
  • 2,906
  • 2
  • 16
  • 24
  • Might be related: https://www.reddit.com/r/cpp/comments/68vhir/whats_the_rationale_for_this_reference_to_local/ – Vittorio Romeo Sep 08 '17 at 10:29
  • 6
    gcc 8.1.1 compiles without complaining. clang 6.0.1 gives an error. – Zulan Jul 24 '18 at 11:09
  • 3
    AFAICS, the fact that (as I can also attest) this now works in `g++` 8 in `-std=c++17` mode implies that either (A) some fix has been treated as a defect and backported, of which I can't find any immediate signs, or (B) `g++` might be allowing it as an extension or even inadvertently. – underscore_d Nov 14 '18 at 22:20
  • 1
    C++20 allows structured bindings to be captured (copying them separately if by value). – Davis Herring Sep 24 '20 at 03:19
  • @DavisHerring, that's interesting, could you give a reference to the change in the standard which allowed this? – ThreeStarProgrammer57 Sep 24 '20 at 10:14
  • 2
    @ThreeStarProgrammer57: [Yes](http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p1091r3.html); note that the restriction on capturing them by reference introduced there was later removed (after further analysis established that no other changes were needed to support them *properly*). – Davis Herring Sep 24 '20 at 13:11

4 Answers4

130

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).

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 5
    A friendly reminder that this issue is resolved. Currently, the tuple-like structured binding introduces variables of unique names, `a` and `b` names the glvalues referred to by those variables. – Passer By Apr 15 '18 at 19:10
  • 2
    @PasserBy is this fix implemented in a current compiler? – scry Aug 27 '18 at 04:35
  • This is fixed by P0588R1 and implemented in GCC 8; @T.C. would you update the answer? – Benjamin Buch Nov 13 '18 at 22:23
  • 3
    @BenjaminBuch It's not at all clear what "fix" you mean. That is the same paper that T.C. already cited as *forbidding* capture of structured bindings into lambdas - which seems very unfortunate to me - at least until the Committee decides on non-placeholder wording to replace this behaviour. Did you mean a different paper? If so, I would appreciate its correct number. – underscore_d Nov 14 '18 at 22:23
  • @underscore_d You are right, I didn't read it carefully and mixed it with something else, sorry! – Benjamin Buch Nov 15 '18 at 22:34
  • @BenjaminBuch I'm pretty sure it *does* need an update, as obviously SBs should be capturable to really be useful, and I think I recall a paper proposing to change the wording on "entities" and such to enable this. On glancing at the final draft, the section on lambda captures seems only to refer to entities, and not to forbid structured bindings (yay), but I'm too tired to be sure I've noticed everything, and I'm definitely not scrolling up to the bit on entities. :) – underscore_d Nov 15 '18 at 22:53
  • 4
    There's a paper in the pipeline that would change this, but nothing has happened to the working paper yet, so there's nothing to update. – T.C. Nov 20 '18 at 08:52
  • 36
    me at each and every phrase: WTF – v.oddou Jun 11 '19 at 09:08
  • 9
    Did anything change for C++20? GCC now allows you to capture variables from structured bindings while clang doesn't. Which compiler is correct? – Ryan Burn Sep 14 '20 at 18:31
  • 5
    *A tuple-like structured binding declaration, on the other hand, does introduce variables.* Clang 12 still does not accept it: https://gcc.godbolt.org/z/9zc8o3scT – Fedor Jun 08 '21 at 07:27
  • 3
    Years passed, Clang 14 still doesn't support this. – facetus May 03 '22 at 02:53
  • 4
    Calng 15 still doesn't work. Clang is weird. – facetus Sep 12 '22 at 18:41
  • 1
    @facetus Clang 16 reporting for duty – sehe Jun 28 '23 at 10:47
  • @sehe Thank you for an update! A couple of more years and maybe Clang will finally implement all C++20 features. – facetus Jul 04 '23 at 12:31
62

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; }();
}();
perimasu
  • 1,025
  • 1
  • 8
  • 16
  • 6
    mind blown . this is like python lambda default argument serving as a neo capture list. note: your stuff also works in clang (which is the only barking compiler of this whole issue) https://godbolt.org/z/PcAZNG – v.oddou Jun 11 '19 at 09:18
  • 2
    Although note that this is more like "captures by value". – user202729 Dec 18 '21 at 07:40
  • I was getting 'Structured binding cannot be captured' error while compiling code using C++ 17 standard. This workaround worked for me. Thanks. And it is OK to capture by value if it is a primitive data type. – Tushar R. May 17 '22 at 07:23
  • 3
    Another (more verbose) option I've occasionally used, that avoids the potential copy, is to capture a pointer. In the above example: `auto r = [a = &a] { return *a; }();` – Oliver Seiler May 19 '22 at 20:08
24

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

Kai Gu
  • 241
  • 2
  • 5
7

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.

fdermishin
  • 3,519
  • 3
  • 24
  • 45