Lambda Implicit Capture Fails with Variable Declared from Structured Binding

Lambda implicit capture fails with variable declared from structured binding

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

Structured binding violations

So they both still violate the rule that that structured bindings are never names of variables, making them never capturable?

No, it is actually clang that is violating the standard, at least for the compiler flags provided.
In C++20, the restriction of not directly supporting captures of structured binding aliases has been lifted, allowing them to be directly used without falling back to constructs using init-captures:

Change [expr.prim.lambda.capture]p8 (7.5.5.2) as follows:

If a lambda-expression explicitly captures an entity that is not odr-usable or captures a structured binding (explicitly or implicitly)
, the program is ill-formed.

Why lambda expression's capture list cannot be decomposed using structured bindings

I'd say this is unspecified by the Standard, but certainly intended to not work. What we know about lambda structure is that, from [expr.prim.lambda.closure]:

The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type

and

The closure type is not an aggregate type

and, from [expr.prim.lambda.capture]:

For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified.

and:

It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. If declared, such non-static data members shall be of literal type.

The intent of having unnamed members is to avoid having them being accessed outside of the lambda's body. The consequence of these members additionally being in unspecified order means as soon as you have more than one capture by copy, you wouldn't even be able to know what your structured binding did.

int a=1, b=2;
auto [x, y] = [a, b]{}; // x==1 or x==2??

The consequence of captures by reference not necessarily naming members means that you wouldn't even know how many identifiers to list in your structured binding declaration.

Since the access of the non-static data members is unspecified, it's possible to have a conforming implementation make them all public, which would satisfy case 3 of structured bindings. But that very much goes against the intent of both the way lambdas are structured and how structured bindings are supposed to work, so I'd be surprised if any implementation knowingly did this. gcc, for instance, explicitly patched to disallow it.

How to emulate structured binding init-capture in lambda C++?

There is no syntax that directly does that.

You can copy the pair and use a structured binding inside the lambda:

auto f = [p]() mutable {
auto& [i, s] = p;

++i;
s += '_';
return std::make_pair(i, s);
};

(Note that the omission of () in front of mutable is not permitted as of C++20.)

Alternatively, you can use .first and .second directly:

auto f = [i = p.first, s = p.second]() mutable {
++i;
s += '_';
return std::make_pair(i, s);
};

OpenMP + clang sometimes fail with a variable declared from structured binding

I cannot answer why clang 11.0.1 does not like this code, though one can see the effect in Compiler Explorer (https://godbolt.org/z/qTTcGf.

However, one can also see there why adding the perverse flag which you think is enabling use of libgomp makes the code compile. The reason is that that flag is disabling OpenMP completely. You can see that there are no calls to OpenMP runtime routines in https://godbolt.org/z/o3TaGz .
I have no idea why that flag is not rejected, since it makes absolutely no sense to ask Clang to link against libgomp, as Clang cannot generate the calls into the GCC OpenMP RTL (these interfaces are different). It is also not documented as a reasonable thing to do (see https://clang.llvm.org/docs/UsersManual.html#openmp-features ) which does not show -fopenmp taking any argument.

You can also see (at https://godbolt.org/z/7hcdrr) that the mainline Clang now accepts your code (without disabling OpenMP) and generates code that includes calls into the OpenMP runtime.

So, overall

  1. Clang 11 has a bug which has been fixed in mainline.
  2. You are passing an unsupported argument to clang and it is then turning off OpenMP compilation.

structured binding with existing vars not possible?

If you want to use existing variables, you have std::tie for that purpose.

std::tie(x, z) = f(); // only works with tuples however

Structured bindings introduce new identifiers. Unfortunately there is nothing equivalent to std::tie for general aggregates.



Related Topics



Leave a reply



Submit