Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Before you throw a rotten tomato

I know the practical application of lambda decomposition is currently limited as one wouldn't be able to find substitution-failure-friendly way to check the number of lambda captures hidden in decomposed variables. This is just a theoretical question as I failed to find any standard part covering the capture member variable access modifiers.

Example

int main() {
    int a;
    auto [x] = [a]{};
    static_cast<void>(a);
    static_cast<void>(x);
    return 0;
}

Standard reference

The standard section about lambda capture is quite long so I might have missed the relevant fragment. What I noticed is that there is an emphasis on that the non-static members which corresponds to the captures are/have to be unnamed.

like image 547
W.F. Avatar asked Sep 26 '17 08:09

W.F.


People also ask

What is structured bindings?

Structured bindings is a new C++ language feature shipping with C++17. It gives us the ability to declare multiple variables initialized from a tuple , pair or struct .

Is Lambda capture a const?

By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.


2 Answers

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.

like image 56
Barry Avatar answered Oct 03 '22 18:10

Barry


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

It actually can. The following

template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;

int main()
{
    auto f = [x = 1, y = 2]() { return x + y; };
    // auto [a, b] = f; // error: cannot decompose lambda closure type 'main()::<lambda()>'

    overload o { f, };
    auto [a, b] = o;

    return b; // returns 2
}

works in GCC trunk https://godbolt.org/z/15c90z.

like image 21
Language Lawyer Avatar answered Oct 03 '22 17:10

Language Lawyer