A problem with generalized lambda captures and pack expansion

268 views
Skip to first unread message

Xeo

unread,
Jul 28, 2013, 1:40:00 PM7/28/13
to std-dis...@isocpp.org
Currently, accoring to N3690, pack-expansion-captures can't use the initializer form. From §5.1.2 [prim.expr.lambda] / 14:

A simple-capture followed by an ellipsis is a pack expansion (14.5.3). An init-capture followed by an ellipsis is ill-formed.

The problem with this is that we may be able to say `[up = std::move(up)]{ ... }` now, but we still can't capture a pack where one of the objects is of a move-only type, leaving us with the same problem we had in C++11, just with packs instead of individual captures now.

Is there a reason why `[xargs = std::move(args)...]{ ... }` was disallowed? The choice doesn't seem particularly arbitary, considering that it was explicitly spelled out.
Additionally, is there already any discussion about changing this so pack-expansion with init-captures is allowed?

Richard Smith

unread,
Jul 28, 2013, 3:18:23 PM7/28/13
to std-dis...@isocpp.org
One problem here is that an init-capture introduces a *named* member of the closure type. A class member name that names a pack would be a new notion, and would bring with it significant additional complications (such as the inability to determine syntactically whether a construct contains an unexpanded parameter pack).

That said, I think it was a mistake to describe init-captures as introducing a /named/ member of the closure type. I don't think it solves the problem it was intended to solve (that is, making the name visible within the body of the lambda) because lookup within the lambda during the initial parse does not find names in the closure type, and the core language might be clearer if they also introduced unnamed members. As such, the above problem is largely artificial, and the difference between init-captures and simple-captures is jarring and doesn't seem to represent any fundamental limitation.

I think a paper exploring these issues and proposing to allow pack-expansions of init-captures would be interesting. It would presumably need to either generalize function parameter packs to also cover init-captures, or to introduce a new form of parameter pack. One strawman possibility, to demonstrate that there's no fundamental problem here, would be to desugar init-captures differently:

  [a(x)..., b] { ... }

could be represented as

  [&](auto ...a) mutable { return [b] { ... }; }(x...)

Xeo

unread,
Jul 28, 2013, 4:25:09 PM7/28/13
to std-dis...@isocpp.org
I don't quite understand the underlying problem. Currently, when capturing a pack, and the name of the pack is found inside the lambda, it also has to be constructed from some kind of member (or members) inside the lambda object. With init-capture, this would just require a different name to be recognized, no?

Richard Smith

unread,
Jul 28, 2013, 5:00:22 PM7/28/13
to std-dis...@isocpp.org
On Sun, Jul 28, 2013 at 1:25 PM, Xeo <hivem...@hotmail.de> wrote:
I don't quite understand the underlying problem. Currently, when capturing a pack, and the name of the pack is found inside the lambda, it also has to be constructed from some kind of member (or members) inside the lambda object. With init-capture, this would just require a different name to be recognized, no?

CWG didn't fully resolve its debate as to the access of the implicit member, and this discussion hinges on that decision (a careful reader will note that N3690 does not actually say what access the named members for init-captures have). Consider this:

template<typename T> void call_f(T t) {
  f(t.x ...)
}

Right now, this is ill-formed (no diagnostic required) because "t.x" does not contain an unexpanded parameter pack. But if we allow class members to be pack expansions, this code could be valid -- we'd lose any syntactic mechanism to determine whether an expression contains an unexpanded pack. This is fatal to at least one implementation strategy for variadic templates. It also admits the possibility of pack expansions occurring outside templates, which current implementations are not well-suited to handle.

Since init-captures add named members to the closure type, allowing init-captures to be pack expansions risks introducing the same problem if those names are visible in *any* context outside the body of the lambda-expression itself.

On Sunday, July 28, 2013 9:18:23 PM UTC+2, Richard Smith wrote:
On Sun, Jul 28, 2013 at 10:40 AM, Xeo <hivem...@hotmail.de> wrote:
Currently, accoring to N3690, pack-expansion-captures can't use the initializer form. From §5.1.2 [prim.expr.lambda] / 14:

A simple-capture followed by an ellipsis is a pack expansion (14.5.3). An init-capture followed by an ellipsis is ill-formed.

The problem with this is that we may be able to say `[up = std::move(up)]{ ... }` now, but we still can't capture a pack where one of the objects is of a move-only type, leaving us with the same problem we had in C++11, just with packs instead of individual captures now.

Is there a reason why `[xargs = std::move(args)...]{ ... }` was disallowed? The choice doesn't seem particularly arbitary, considering that it was explicitly spelled out.
Additionally, is there already any discussion about changing this so pack-expansion with init-captures is allowed?

One problem here is that an init-capture introduces a *named* member of the closure type. A class member name that names a pack would be a new notion, and would bring with it significant additional complications (such as the inability to determine syntactically whether a construct contains an unexpanded parameter pack).

That said, I think it was a mistake to describe init-captures as introducing a /named/ member of the closure type. I don't think it solves the problem it was intended to solve (that is, making the name visible within the body of the lambda) because lookup within the lambda during the initial parse does not find names in the closure type, and the core language might be clearer if they also introduced unnamed members. As such, the above problem is largely artificial, and the difference between init-captures and simple-captures is jarring and doesn't seem to represent any fundamental limitation.

I think a paper exploring these issues and proposing to allow pack-expansions of init-captures would be interesting. It would presumably need to either generalize function parameter packs to also cover init-captures, or to introduce a new form of parameter pack. One strawman possibility, to demonstrate that there's no fundamental problem here, would be to desugar init-captures differently:

  [a(x)..., b] { ... }

could be represented as

  [&](auto ...a) mutable { return [b] { ... }; }(x...)

--
 
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
 
 

Johannes Schaub

unread,
Jul 28, 2013, 5:22:21 PM7/28/13
to std-dis...@isocpp.org
2013/7/28 Xeo <hivem...@hotmail.de>:
> I don't quite understand the underlying problem. Currently, when capturing a
> pack, and the name of the pack is found inside the lambda, it also has to be
> constructed from some kind of member (or members) inside the lambda object.
> With init-capture, this would just require a different name to be
> recognized, no?
>

My understanding is that this

[foo...]

Does not actually "capture a pack". It instead captures all the
individual elements of the pack after it is being expanded. When
"foo..." is used within the lambda body, it first is expanded as usual
(and each element for a short time refers to the entities in the
reaching scope of the lambda), and afterwards, the names will refer to
the unnamed data members synthesized by the expansion of "foo" in the
capture list, because each expansion element of the pack corresponds
to one captured entity 1:1.

When doing [bar = foo + 1...], inside of the lambda when you are doing
"bar..." inside of the lambda body you don't have anything in the
reaching scope to expand - "bar" will need to be some voodo-declared
pack, the voodo of which needs to be defined by the committee for this
to work.

Xeo

unread,
Jul 28, 2013, 5:23:20 PM7/28/13
to std-dis...@isocpp.org
Ah, I understand, that is most certainly a problem. It's not so much about the implementation of the pack expansion itself, and the name lookup, but more about the pack actually having a user-defined name inside of the lambda. Thanks for the explanation.

That said, it would really be a shame to have to resort to tuples to actually capture packs, although atleast there's a work-around available this time, thanks to individual init-captures. Hopefully, though, it would be possible to just have the capture-members be private and that's it. I have a feeling this is going more and more into the territory of abusing lambda-expressions as general-purpose class-/object-generators. OTOH, maybe there's a reason and need for a more concise syntax for such things?

Gabriel Dos Reis

unread,
Jul 28, 2013, 4:17:09 PM7/28/13
to std-dis...@isocpp.org
since templates in C++ aren't first class thingies, I would think the problem
with packs is only artificial: indeed the whole transformation of
lambdas into classes operates on *instantiations* with ground types, not
on the abstract template definitions. So, I think a rule that allows
pack captures should be just fine since that would apply only to the
concrete lambdas which no known unknown (packs) left.

Gabriel Dos Reis

unread,
Jul 29, 2013, 2:53:14 AM7/29/13
to std-dis...@isocpp.org
Xeo <hivem...@hotmail.de> writes:

| That said, it would really be a shame to have to resort to tuples to actually
| capture packs, although atleast there's a work-around available this time,
| thanks to individual init-captures.

I don't see this data member packs problem. Most semantics processing,
in partciular for lambda captures, is supposed to happen on the
result of the template *instantiation*, at which point there is no packs...

-- Gaby
Reply all
Reply to author
Forward
0 new messages