156

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 ?

6
  • Might be related: reddit.com/r/cpp/comments/68vhir/… Commented Sep 8, 2017 at 10:29
  • 6
    gcc 8.1.1 compiles without complaining. clang 6.0.1 gives an error. Commented Jul 24, 2018 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. Commented Nov 14, 2018 at 22:20
  • 1
    C++20 allows structured bindings to be captured (copying them separately if by value). Commented Sep 24, 2020 at 3:19
  • 2
    @ThreeStarProgrammer57: Yes; 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). Commented Sep 24, 2020 at 13:11

4 Answers 4

142

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

Sign up to request clarification or add additional context in comments.

14 Comments

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.
@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.
me at each and every phrase: WTF 😱😱
Did anything change for C++20? GCC now allows you to capture variables from structured bindings while clang doesn't. Which compiler is correct?
A tuple-like structured binding declaration, on the other hand, does introduce variables. Clang 12 still does not accept it: gcc.godbolt.org/z/9zc8o3scT
|
70

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; }();
}();

4 Comments

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) godbolt.org/z/PcAZNG
Although note that this is more like "captures by value".
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.
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; }();
27

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

6 Comments

Nice, thanks, if you get a chance it would be great to link the relevant section in the standard.
@Silviu, the this link states "Structured bindings cannot be captured by lambda expressions. (until C++20)"
Clang 14 still doesn't support it, even if compiled with -std=c++20. GCC is the best, Clang...
Clang 15 still doesn't work.
Works in clang trunk: godbolt.org/z/j4E4oYn83
|
10

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.