7

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.

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • I don't really understand what the purpose of `auto [x] = [a]{};` would be. What are you trying to do there? What would `x` be? – Some programmer dude Sep 26 '17 at 08:18
  • @Someprogrammerdude I perceive a lambda expression as a inline class definition that contain a state which comes from lambda capture. The state of plain structure can be bound into structure binding variables. I was just curious is there a way to use a lambda as a plain structure in this sense. – W.F. Sep 26 '17 at 08:22
  • @Someprogrammerdude I think in case of `auto [x] = [a]{};` `x` would correspond to `a`. – W.F. Sep 26 '17 at 08:23
  • 4
    So what you want is simply to copy the value of `a` (as stored in the lambda object) into `x`? Then the problem have nothing to do with captures but with that you can not decompose objects in general. The "state" is *private* to the lambda object. – Some programmer dude Sep 26 '17 at 08:24
  • @Someprogrammerdude yes it is quite natural that the state of lambda might be private. But couldn't find the relevant standard part covering this. – W.F. Sep 26 '17 at 08:27
  • While it's not from the specification, [this structured binding reference](http://en.cppreference.com/w/cpp/language/structured_binding) explains it: "if ***E*** is a non-union class type but `std::tuple_size` is not a complete type, then the names are bound to the ***public data*** members of ***E***. " (The "public data" part emphasized by me). It's explained in detail in [case 3](http://en.cppreference.com/w/cpp/language/structured_binding#Case_3:_binding_to_public_data_members). – Some programmer dude Sep 26 '17 at 08:31
  • @Someprogrammerdude yes but there's nothing about access modifiers of lambda data members corresponding to captures... you see my point? – W.F. Sep 26 '17 at 08:32
  • 3
    The access was irrelevant when you can't name them...until it isn't. Regardless, this is certainly not meant to work. – T.C. Sep 26 '17 at 08:38
  • 1
    Related? [C++: Reject lambda closure types in decompositions (PR c++/78896)](https://patchwork.ozlabs.org/patch/719880/). – dfrib Sep 26 '17 at 08:39
  • 2
    Do you actually have realistic use cases for this? – user7860670 Sep 26 '17 at 08:40
  • @VTT As I already mentioned it is just a theoretical question. If the structured bindings allow to use some joker matching all the *following member variables* I would find some exciting use cases... – W.F. Sep 26 '17 at 08:43
  • 2
    [This lambda reference](http://en.cppreference.com/w/cpp/language/lambda) says that the order of the captured variables inside the generated type is *unspecified*. While it doesn't disallow decomposition in general, it makes it kind of worthless. If you capture *two* (or more) variables then you would not know in which order they would be bound using structured binding. If you have e.g. `int a; float b; auto [x, y] = [a, b](){};` then would `x`correspond to `a`? You don't know, it might or it might not. – Some programmer dude Sep 26 '17 at 08:44
  • @T.C. So this is a standard defect coming from the past inability to do something like structured bindings? – W.F. Sep 26 '17 at 08:45
  • @Someprogrammerdude but you could extract some interesting lambda traits – W.F. Sep 26 '17 at 08:46
  • 2
    Aww man, what am I supposed to do this create full of rotten tomatoes now? – StoryTeller - Unslander Monica Sep 26 '17 at 09:01
  • 1
    @StoryTeller you can still throw them at politicians :) – W.F. Sep 26 '17 at 09:02
  • [The standard allows an implementation to make a closure type not standard-layout](https://timsong-cpp.github.io/cppwp/n4659/expr.prim.lambda#closure-2) by placing data members into private section, for example. This would make lambda-expression undecomposable. – Language Lawyer Apr 29 '19 at 23:59
  • @user7860670 I have an use case - in a callback system, I always pass a context pointer for tracking ownership over time. This context pointer is almost always duplicated in the lambda, e.g. `some_signal.bind(this, [this, foo] (int val) { this->some_callback(foo + val); });` .... I'd like to detect whether the context pointer is already stored in the lambda to avoid duplicating it internally. – Jean-Michaël Celerier Jul 13 '23 at 16:07

2 Answers2

5

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
2

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.

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29
  • 2
    Unfortunetly is not supported by clang so if standard doesn't allow this one cannot rely on it :(. Racionalisation of Barry seem to be reasonable and I guess intention of standard guys was to keep it inaccessible. – W.F. Apr 30 '19 at 07:58
  • @W.F. do you have gcc bugzilla account? – Language Lawyer May 01 '19 at 23:59
  • you got me wrong. I'm not saying gcc has a bug, just it is a free interpretation wether it should or shouldn't work and as such one can't rely on the behavior. – W.F. May 02 '19 at 15:59
  • @W.F. _you got me wrong. I'm not saying gcc have a bug_ I didn't think you're saying it is a bug in GCC. Yes, an implementation has freedom to support or not support this. Most implementations decided not to. And GCC kinda tried to move in the same direction, but failed. So I suppose they intend not to support this. – Language Lawyer May 02 '19 at 16:01
  • Oh I see now. Bad luck, but I have the account only on clang bug tracker. Maybe someone will be able to help us on this...(?) – W.F. May 02 '19 at 16:05