RFC: Unpacking tuples to value sequences

396 views
Skip to first unread message

Matthew Woehlke

unread,
Jan 18, 2016, 4:38:11 PM1/18/16
to std-pr...@isocpp.org
Okay, after Vicente convinced me I should write this, I present to you
the start of a paper proposing a '[*]' operator (not pushed yet, and
just .rst for now; sorry).

The general gist is:

struct Datum { int id; double value; };

auto d1 = Datum{42, 2.7};
auto d2 = Datum{[*]d1};
foo([*]d1, [*]d2);

// equivalent to:
auto d2 = Datum{get<0>(d1), get<1>(d2)};
foo(get<0>(d1), get<1>(d1), get<0>(d2), get<1>(d2));

(Yes, the first case is silly; it's more interesting if instead of a
Datum in both cases, the second was a different but layout-compatible
type. The paper has a somewhat better version of the example.)

As described in the paper, there is at least one use case for this
language feature that cannot be (easily) accomplished otherwise:

template <type T>
foo(T const& tuple_like)
{
new Bar{next_id(), [*]tuple_like};
}

If Bar cannot take a T directly (e.g. because it is an external library
type), but the 'tuple size' of T is not known, it is impossible to write
a single generic definition of foo() (and hard, or at least very
annoying, to write specializations).

I haven't written a proposed wording yet, and not sure if I will be able
to until "tuple like" is specified. (BTW, is there an official paper on
that yet?)

Comments, as always, welcome. Motivating use cases especially welcome :-).

--
Matthew
PXXXX Value Sequence Unpacking.rst

Nicol Bolas

unread,
Jan 18, 2016, 6:20:18 PM1/18/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
This is a good first try, but I think you need to go much deeper. I would envision handling reverse-aggregate expansion exactly like template parameter packs: with `...`. Which also means you get to unpack entire expressions that use the pack.

Of course, a problem shows up. Many types are reverse-aggregate types, so `...` could start unpacking things you don't expect. Plus, there's already existing code that expects `...` to *not* unpack reverse aggregates. So, we need a way to explicitly say that you want to use a specific expression as a value sequence. That would be where a notation like `[*]` would be useful.

So, let's say you have this:


template<typename ...Args)
func
(Args&& ...pack)
{
 
auto rev_agg = ...; //Some reverse-aggregate.

 
auto test1 = make_tuple(func(rev_agg)...); //Compile error; nothing in the expression is a value sequence.
 
auto test2 = make_tuple(func([*]rev_agg)...); //Success. Treats `rev_agg` as though it were a parameter pack.
 
auto test3 = make_tuple(func(rev_agg, pack)...); //Success. `rev_agg` gets no special treatment. Every call to `func` gets the same `rev_agg`, just as it would in current code.
 
auto test4 = make_tuple(func([*]rev_agg, pack)...); //Success. Unpacks both `rev_agg` and `pack`. Only works if the two are the same size, just like for two parameter packs.
}

See what I'm getting at?

If you're going to have unpacking of tuples, it needs to be able to work just like unpacking parameter packs. That makes it so much more useful.

Also, it'd be useful to build parameter packs without having to use `make_tuple`:

auto ...stuff = {func([*]rev_agg)...};

Here, `stuff` is not a tuple. It is exactly equivalent to a template parameter pack.

Of course, structured binding can key off of this too:

auto {x, y, z} = {func([*]rev_agg)...};

Naturally, this also means that this works too:

auto (w, u, v} = {pack...};

The goal here is to be able to control two different operations: transforming a type into a value sequence and unpacking a value sequence. We already have the latter, if we consider parameter packs to simply be a special case of value sequences. We simply need syntax to turn a type into a value sequence.

That's what `[*]` does; it turns the given expression into a value sequence, which is a construct that works exactly like a parameter pack. So it must be unpacked at some point, but not necessarily at the moment it is generated.

Jens Åkerblom

unread,
Jan 19, 2016, 1:41:40 AM1/19/16
to ISO C++ Standard - Future Proposals
Seems similar to my idea regarding structure bindings. Look for "Structured Bindings with Variadic Templates". The base idea there is to allow you to unpack (not only tuples) like:

<code>
<auto... t> = make_tuple(foo()...);
</code>

I decided to put the proposal on ice until the structured bindings proposal has matured.

Matthew Woehlke

unread,
Jan 19, 2016, 10:21:25 AM1/19/16
to std-pr...@isocpp.org
On 2016-01-19 01:41, Jens Åkerblom wrote:
> Seems similar to my idea regarding structure bindings.

No, really it isn't. Structured binding and value sequence unpacking are
related, but significantly different. I tried to make that clear in the
beginning of the Rationale.

What can I say to make this clearer?

--
Matthew

Nicol Bolas

unread,
Jan 19, 2016, 10:29:01 AM1/19/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

He's talking about something he suggested earlier, which he calls a form of "structured binding", but it's really something else. It's the ability to convert a tuple into a named parameter pack, via a syntax that looks somewhat like structured binding.

You made some posts in that thread a month back, so you should be aware of it.

Nicol Bolas

unread,
Jan 19, 2016, 10:40:26 AM1/19/16
to ISO C++ Standard - Future Proposals

 The problem with your idea is two fold:

1) It really has nothing to do with structured bindings at all. Well, it may internally use the same configurable constructs to access the members when using the value sequence. But otherwise, it doesn't really interact with structured bindings. You're simply piggy-backing off the syntax with your `<auto... t>` stuff. You could just as easily use `auto... t` instead.

2) It has the problem that I aluded to earlier in this thread: how to know when a user meant to unpack reverse-aggregate and when the user meant to pass it as a type. Your syntax cannot distinguish between what mine can. Which are these cases:

tuple<int, float> f = ...
tuple
<bool, std::string> g = ...

auto ...thing1 = func([*]f, [*]g)...;
auto ...thing2 = func([*]f, g)...;
auto ...thing3 = func(f, [*]g)...;
auto ...thing4 = func(f, g)...;

For `thing1`, you create a value sequence from `func(int, bool), func(float, string)` calls.

For `thing2`, you create a value sequence from `func(int, tuple<bool, std::string>), func(float, tuple<bool, std::string>)`calls. `thing3` is quite similar, except for the values of `g`.

`thing4` is a compile error, since neither `f` nor `g`

Remember, if we allow any possible reverse-aggregate (all public members, no virtuals) to be unpacked in expressions, then we really need to be able to control which particular values are intended to be unpacked and which are not. The `[*]` syntax is used to designate an expression to be able to be unpacked as a value sequence.

Matthew Woehlke

unread,
Jan 19, 2016, 11:02:15 AM1/19/16
to std-pr...@isocpp.org
On 2016-01-19 10:29, Nicol Bolas wrote:
> On Tuesday, January 19, 2016 at 10:21:25 AM UTC-5, Matthew Woehlke wrote:
>> On 2016-01-19 01:41, Jens Åkerblom wrote:
>>> Seems similar to my idea regarding structure bindings.
>>
>> No, really it isn't. Structured binding and value sequence unpacking are
>> related, but significantly different. I tried to make that clear in the
>> beginning of the Rationale.
>>
>> What can I say to make this clearer?
>
> He's talking about something he suggested earlier
> <https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/CcOGSKEoQa0>,
> which he *calls* a form of "structured binding", but it's really something
> else. It's the ability to convert a tuple into a named parameter pack, via
> a syntax that *looks* somewhat like structured binding.

Yes, I found the thread again, and have some recollection of it :-).

I can understand the argument that it "isn't structured binding, per
se". Unless I am really confused, however, it's still *a* form of
binding, which is (mostly) orthogonal to my proposal, besides that they
both deal with (different aspects of) very similar concepts.

On further consideration, however, I can see where my proposal might
make the one referenced mostly irrelevant. IOW, if this:

auto {x, y, z...} = some_tuple;

...translated to:

auto x = get<0>(some_tuple);
auto y = get<1>(some_tuple);
auto z = make_tuple([2:]some_tuple);

...then Jens' proposal becomes much simplified and doesn't need to
involve mucking about with parameter packs. But it would depend on mine
in order to be able to use the "trailing bits" in the same ways that
were proposed in that thread.

(Please ignore various errors and gratuitous use of novel syntax in the
above; it is just for illustrative purposes.)

> You made some posts in that thread a month back, so you should be aware of
> it.

My comments seem to have drifted from the "variadic" part and relate to
"what is tuple-like?". (And... I mentioned this idea there, also :-).)

--
Matthew

Nicol Bolas

unread,
Jan 19, 2016, 11:10:55 AM1/19/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
I want to explain my idea a bit more rigorously, because if I do, I think it can make it more clear what some things mean.

A value sequence is a sequence of expressions which can be unpacked. Function parameter packs and non-type template parameter packs are value sequences. Value sequences have a known length.

A value sequence expression is an expression involving a value sequence and/or type/template template parameter packs. These are effectively "patterns" in parameter pack unpacking. Any expression that acts on a value sequence or other parameter pack becomes a value sequence expression. If two separate value sequence expressions form a larger value sequence expression, then both sequences must have the same length.

A value sequence expression can be stored in a name with the following syntax:

auto... name = <value sequence expression>

`name` is itself a value sequence, which can later be used to create further value sequence expressions.

The length of a value sequence can be determined with:

sizeof...(<value sequence>)

A value sequence expression can be unpacked into a regular expression with the `...` syntax:

<value sequence expression>...

This creates a comma-separated expression, just as it did with pattern unpacking in regular parameter packs. If the value sequence expression contains type or template template parameter packs, then they too are expanded, again as before for parameter packs.

An expression can be converted into a value sequence expression with the value sequence conversion operator `[*]`. The type of the expression determines the specifics of how to get the sequence of expressions when it comes time to later unpack them. This functionality uses the same customization point as structured binding for accessing the members of the type, as well as getting the length of the value sequence.

Structured binding is currently defined to work on types, and that shouldn't change. But it should also be allowed to work on value sequence expressions:

auto {x, y, z} = args; //Function parameter pack decomposed into 3 elements.
auto {x, y, z} = unpackable; //Unpackable type stored via normal structured binding.
auto {x, y, z} = [*]unpackable; //Value sequence stored via structured binding. Identical to previous statement.

Nicol Bolas

unread,
Jan 19, 2016, 11:14:54 AM1/19/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
It should be noted that this changes some of my code from before:

auto {x, y, z} = {func([*]rev_agg)...};

Becomes:

auto {x, y, z} = func([*]rev_agg);

There's no need for unpacking here, since it's just a value sequence.

Matthew Woehlke

unread,
Jan 19, 2016, 11:15:37 AM1/19/16
to std-pr...@isocpp.org
On 2016-01-18 18:20, Nicol Bolas wrote:
> This is a good first try, but I think you need to go much deeper. I would
> envision handling reverse-aggregate expansion *exactly* like template
> parameter packs: with `...`. Which also means you get to unpack entire
> expressions that use the pack.
>
> Of course, a problem shows up. Many types are reverse-aggregate types, so
> `...` could start unpacking things you don't expect. Plus, there's already
> existing code that expects `...` to *not* unpack reverse aggregates. So, we
> need a way to explicitly say that you want to use a specific expression as
> a value sequence. That would be where a notation like `[*]` would be useful.

Yes, I think it needs to use some syntax other than `...` for various
reasons. Conflict with parameter packs is one. Extensibility (see Future
Directions), e.g. to slicing, is another. (Huh. And if we get that,
`[5]t` becomes syntax sugar for `get<5>(t)`, which is kinda nice...)

BTW, what is a "reverse aggregate"? I don't think I'm familiar with the
term. I am guessing you mean "tuple-like"?

> So, let's say you have this:
>
> template<typename ...Args)
> func(Args&& ...pack)
> {
> auto rev_agg = ...; //Some reverse-aggregate.
>
> auto test2 = make_tuple(func([*]rev_agg)...); //Success. Treats `rev_agg`
> as though it were a parameter pack.

If that works, I would expect this to work also:

make_tuple(func(1, 2, a, b)...);

...but that seems silly, and at best should be an orthogonal proposal.
Moreover, I don't see it as worth a language change. Even though it's
considerably more verbose, this seems like a job for a transform function:

auto temporary_tuple = std::apply_each(func, args...);
foo([*]temporary_tuple);

...where `args...` is any expression sequence, e.g. `[*]rev_agg`.
(apply_each is a hypothetical function that takes a functor and a
parameter pack and returns a tuple where each element of the result is
the return value of the functor applied to each input value.)

IOW, if I were on the committee, I would insist on a separate rationale
why that specific example warrants a(n additional) language change
beyond my proposal as presented. Do you have such a rationale?

> auto test4 = make_tuple(func([*]rev_agg, pack)...); //Success. Unpacks
> both `rev_agg` and `pack`. Only works if the two are the same size, just
> like for two parameter packs.

Wrong. As the proposal is currently written, the effect of the above
code is well defined to be:

make_tuple(
func(get<0>(rev_agg), get<1>(rev_agg), ..., pack<0>),
func(get<0>(rev_agg), get<1>(rev_agg), ..., pack<1>),
...
);

Changing the meaning in this context makes the feature much more
complicated, and precludes using it to achieve the above effect. If you
really need the effect you described, use (hypothetical) std::zip:

auto zipped = std::zip(rev_agg, make_tuple(pack...));
auto test4 =
std::apply_each([](auto pair){ return func([*]pair); }, zipped);

(std::zip of course returns a tuple of pairs, where the first of each
pair is from the first tuple[-like], and the second of each pair is from
the second tuple[-like].)

> See what I'm getting at?

Yes, but I don't agree. You are suggesting to make the language feature,
which in my proposal is straight forward,

> If you're going to have unpacking of tuples, it *needs* to be able to work
> just like unpacking parameter packs. That makes it so much more useful.

I do see the utility of something like apply_each. I don't so much see
why it needs to be a language feature rather than a library feature.
Again, please provide a rationale to back up such claims.

(Note that I gave one example where achieving the same effect as with
the proposed `[*]` would be very, very hard. I don't see that for what
you are suggesting.)

> Also, it'd be useful to build parameter packs without having to use
> `make_tuple`:
>
> auto ...stuff = {func([*]rev_agg)...};
>
> Here, `stuff` is not a tuple. It is exactly equivalent to a template
> parameter pack.

See above comments. Also, this smells very much like an orthogonal
feature. Why shouldn't I be able to write:

auto ...stuff = {12, "brown", 53.5};

...? (Actually, this feels very much like Jens' proposal...)

> Naturally, this also means that this works too:
>
> auto (w, u, v} = {pack...};

Doesn't this already work with P0144R0? Again, orthogonal!

> The goal here is to be able to control two different operations:
> transforming a type into a value sequence and unpacking a value sequence.
> We already have the latter, if we consider parameter packs to simply be a
> special case of value sequences. We simply need syntax to turn a type into
> a value sequence.
>
> That's what `[*]` does; it turns the given expression into a value
> sequence,

Right.

> which is a construct that works exactly like a parameter pack. So
> it must be unpacked at some point, but not necessarily at the moment it is
> generated.

That's not how it's proposed. I don't see the utility, and in fact, I
feel that raises the same issue as `std::unpacked`, namely, creating an
ambiguity when it *should* be unpacked, which is specifically a point I
wish to *avoid*.

I'm very tempted to take Vicente's approach here, and suggest that you
write a counter-proposal if you feel strongly about these matters.

--
Matthew

Matthew Woehlke

unread,
Jan 19, 2016, 11:31:31 AM1/19/16
to std-pr...@isocpp.org
On 2016-01-19 10:40, Nicol Bolas wrote:
> auto ...thing1 = func([*]f, [*]g)...;

I alluded to this in my other reply, but I think it bears repeating...
what does this (RHS) mean?

- { func(f<0>, g<0>), func(f<1>, g<1>) }
- { func(f<0>, f<1>, g<0>), func(f<0>, f<1>, g<1>) }
- { func(f<0>, g<0>, g<1>), func(f<1>, g<0>, g<1>) }
- func(f<0>, f<1>, g<0>, g<1>)

I could legitimately want *any* of those. Conflating concepts (template
argument packs and value sequence unpacking of tuple-likes) makes it
impossible to specify which. Not to mention that I'm not convinced the
syntax isn't overly obtuse.

If, instead, we have apply_each (and zip), I can be precise:

- apply_each([](auto p){return func([*]p);}, [*]zip(f, g))
- apply_each([](auto v){return func([*]f, v);}, [*]g)
- apply_each([](auto v){return func(v, [*]g);}, [*]f)
- func([*]f, [*]g)

Now there is no ambiguity as to my intent.

--
Matthew

Nicol Bolas

unread,
Jan 19, 2016, 11:44:05 AM1/19/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Tuesday, January 19, 2016 at 11:31:31 AM UTC-5, Matthew Woehlke wrote:
On 2016-01-19 10:40, Nicol Bolas wrote:
> auto ...thing1 = func([*]f, [*]g)...;

I alluded to this in my other reply, but I think it bears repeating...
what does this (RHS) mean?

  - { func(f<0>, g<0>), func(f<1>, g<1>) }
  - { func(f<0>, f<1>, g<0>), func(f<0>, f<1>, g<1>) }
  - { func(f<0>, g<0>, g<1>), func(f<1>, g<0>, g<1>) }
  - func(f<0>, f<1>, g<0>, g<1>)

I could legitimately want *any* of those.

And my syntax can provide *all* of them:


func([*]f, [*]g)...;
func([*]f..., [*]g)...;
func([*]f, [*]g...)...;
func([*]f..., [*]g...);

So what's the problem?
 
Conflating concepts (template
argument packs and value sequence unpacking of tuple-likes) makes it
impossible to specify which. Not to mention that I'm not convinced the
syntax isn't overly obtuse.

If, instead, we have apply_each (and zip), I can be precise:

  - apply_each([](auto p){return func([*]p);}, [*]zip(f, g))
  - apply_each([](auto v){return func([*]f, v);}, [*]g)
  - apply_each([](auto v){return func(v, [*]g);}, [*]f)
  - func([*]f, [*]g)

Now there is no ambiguity as to my intent.

No ambiguity, perhaps. But lots and lots of heinous syntax.

Matthew Woehlke

unread,
Jan 19, 2016, 12:11:03 PM1/19/16
to std-pr...@isocpp.org
On 2016-01-19 11:44, Nicol Bolas wrote:
> On Tuesday, January 19, 2016 at 11:31:31 AM UTC-5, Matthew Woehlke wrote:
>> On 2016-01-19 10:40, Nicol Bolas wrote:
>>> auto ...thing1 = func([*]f, [*]g)...;
>>
>> I alluded to this in my other reply, but I think it bears repeating...
>> what does this (RHS) mean?
>>
>> - { func(f<0>, g<0>), func(f<1>, g<1>) }
>> - { func(f<0>, f<1>, g<0>), func(f<0>, f<1>, g<1>) }
>> - { func(f<0>, g<0>, g<1>), func(f<1>, g<0>, g<1>) }
>> - func(f<0>, f<1>, g<0>, g<1>)
>>
>> I could legitimately want *any* of those.
>
> And my syntax can provide *all* of them:
>
> func([*]f, [*]g)...;
> func([*]f..., [*]g)...;
> func([*]f, [*]g...)...;
> func([*]f..., [*]g...);

Why do I have to unpack it *twice*? Ugh...

> [...] lots and lots of heinous syntax.

I find your proposal much too complicated and severely lacking in
rationale why we need such as a *language* feature. (I have yet to see
you offer a rationale, besides "hey, wouldn't it be great if...".) As
such, I cannot support it in good conscience, and so I will reiterate my
earlier comment: If you feel strongly, I recommend that you write and
present a competing paper.

Unless you can provide a better justification, at this time I intend to
continue with my version in its current form.

--
Matthew

Nicol Bolas

unread,
Jan 19, 2016, 2:15:11 PM1/19/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Tuesday, January 19, 2016 at 12:11:03 PM UTC-5, Matthew Woehlke wrote:
On 2016-01-19 11:44, Nicol Bolas wrote:
> On Tuesday, January 19, 2016 at 11:31:31 AM UTC-5, Matthew Woehlke wrote:
>> On 2016-01-19 10:40, Nicol Bolas wrote:
>>> auto ...thing1 = func([*]f, [*]g)...;
>>
>> I alluded to this in my other reply, but I think it bears repeating...
>> what does this (RHS) mean?
>>
>>   - { func(f<0>, g<0>), func(f<1>, g<1>) }
>>   - { func(f<0>, f<1>, g<0>), func(f<0>, f<1>, g<1>) }
>>   - { func(f<0>, g<0>, g<1>), func(f<1>, g<0>, g<1>) }
>>   - func(f<0>, f<1>, g<0>, g<1>)
>>
>> I could legitimately want *any* of those.
>
> And my syntax can provide *all* of them:
>
> func([*]f, [*]g)...;
> func([*]f..., [*]g)...;
> func([*]f, [*]g...)...;
> func([*]f..., [*]g...);

Why do I have to unpack it *twice*? Ugh...

Because that's what you're doing. Take the second case. You want to unpack `f` within the argument list. But you want to unpack `g` across `func`. That's two separate unpack steps, so it needs two separate unpack operations.

Your mechanism is too simplified to be of general use. There are legitimate reasons for people to use all of these forms, and your particular use case(s) are not sufficiently important to force users to resort to arbitrary other methods to get them.

We'd be better off with no unpacking syntax than with a half-baked one.

> [...] lots and lots of heinous syntax.

I find your proposal much too complicated and severely lacking in
rationale why we need such as a *language* feature. (I have yet to see
you offer a rationale, besides "hey, wouldn't it be great if...".)

... The only part of your proposal that absolutely needs to be a *language* feature is the ability to unpack a tuple into a braced-init-list. You could always create a variadic version of `apply` that walks through the argument list and unpacks any tuples it sees. So the only place in your proposal that absolutely needs it to be a part of the language is braced-init-lists.

So if we're going by what absolutely cannot possibly be done without a language feature, then your proposal is really no more motivated than mine. Since mine also includes that, but much more.

Furthermore, if that were the standard by which features were approved, we wouldn't have lambdas. Because there is nothing there that couldn't be done with structs declared within the function. In-function structs couldn't participate in template deduction in C++03, which is why they couldn't have been used in many cases. So technically, all C++11 had to do was allow them to participate like any other type. And in fact, the committee did this. Yet they still provided lambdas anyway. Why?

Because it's a highly useful feature for a wide variety of circumstances that makes code so much easier to write. Same goes for range-for and many other things.

And if we continue with your logic, we should only have allowed `...` to be applied to the pack itself, rather than to expression "patterns". After all, we could always find ways to metaprogram around unpacking patterns. No need to create confusion if you want to do `func(args)...` to call the function multiple times with each argument. No, we clearly should have made people say `broadcast(func, args...)` instead.

Yet we didn't. We allowed `...` to expand almost any expression. Why?

Because it was exceptionally useful.

So clearly the standard for what should be a language feature is not merely "things that cannot be a library feature."

However, if you really want something that absolutely must be a language feature, there's this: efficiency.

In a different post, I pointed out this use case:


auto test4 = make_tuple(func([*]rev_agg, pack)...);

Where `pack` is a function parameter pack. This calls `func` for each pair of values, capturing them in a tuple.

And you said you could do it under your syntax with:


auto zipped = std::zip(rev_agg, make_tuple(pack...));
auto test4 =
  std
::apply_each([](auto pair){ return func([*]pair); }, zipped);

So, step 1 is to take the elements of the function parameter pack and make a tuple out of them. Well, that's going to copy/move some of them into a temporary. Then, we create a tuple of pairs of those values, which copy/moves them again. It also copies out of `rev_agg` for no reason. The temporary is then destroyed.

Or you can do what I said, which will always boil down to the most efficient code. No temporaries are created. It's just a sequence of `get<>` calls.

Oh, and proper argument forwarding can be used:

auto test4 = make_tuple(func([*]rev_agg, forward<Args>(pack))...);

Even if you change your version to use `forward_as_tuple`, you're still using copying out of `rev_agg`. Whereas mine just uses `get<I>` directly in situ. No pointless lambdas, no zipping or copying. It boils down to exactly what you want and no more.

In the best possible case, where `zip` returns a tuple of references (which kinda breaks `forward_as_tuple`), you're still creating a needless temporary, which takes up unnecessary room on the stack.

Your way costs both memory and runtime performance. Mine does not. Mine doesn't make you pay for something you don't have to.

Oh, and the temporary in this case must be named. Weren't you one of those who were saying that naming things is hard, that it's a cost we have to pay, so we should be able to return unnamed types ;) (note: I'm not against the temporary because it's named. I'm against it because it's taking up space and performance. The name is irrelevant to me).

Matthew Woehlke

unread,
Jan 19, 2016, 3:23:25 PM1/19/16
to std-pr...@isocpp.org
On 2016-01-19 14:15, Nicol Bolas wrote:
> Your mechanism is too simplified to be of *general* use.

I disagree. I can think of immediate examples of unpacking in place (and
some have been given). I would like to see a reasonable example for:

auto result = make_tuple(func([*]f..., [*]g)...);

...along with a rationale why this merits a language feature.

p.s. If we have genuine promotion of value sequences to parameter packs,
why can't we do this?

auto&&... g_unpacked = g;
auto result = make_tuple(func([*]f, g_unpacked)...);

This is only slightly more verbose, but doesn't make [*] unreasonably
complicated. I also can't think of why this shouldn't be equally
efficient; the compiler ought to be able to inline use of `g_unpacked`
to use `g` directly.

Or, maybe a similar syntax can be used to indicate the intent to turn a
tuple-like into a parameter pack in place. This would even be orthogonal
(or at least, in addition to) my proposal.

> There are legitimate reasons for people to use all of these forms,
> and your particular use case(s) are not sufficiently important to
> force users to resort to arbitrary other methods to get them.

I could (and will) say the same thing about your proposal.

> ... The *only part* of your proposal that absolutely needs to be a
> *language* feature is the ability to unpack a tuple into a
> braced-init-list.

I think you're severely understating the usefulness of this case. I
don't feel that being able to unpack into e.g. constructor calls is so
rare as to be so lightly glossed over.

> You could always create a variadic version of `apply`
> that walks through the argument list and unpacks any tuples it sees.

You can't *just* do that. You have to annotate somehow when you want
something unpacked and when you don't... which leads to Vicente's
proposal and a "magic type" that has special meaning in
std::experimental::apply / std::invoke. And also, more of that clumsy
syntax you claim to dislike so much.

> So if we're going by what absolutely cannot possibly be done without a
> language feature, then your proposal is really no more motivated than mine.
> Since mine also includes that, but much more.

I at least gave a motivating example (conversion between different but
layout-compatible aggregates). I'm still waiting on a realistic example
from you.

BTW, here is a random, trivially located example from real, production
code¹ where I wish I had `[*]`:

out->SetPosition(center[0], center[1], center[2]);
out->SetViewUp(up[0], up[1], up[2]);
out->SetFocalPoint(focus[0], focus[1], focus[2]);

// out->SetPosition([*]center);
// out->SetViewUp([*]up);
// out->SetFocalPoint([*]focus);

https://github.com/Kitware/maptk/blob/master/gui/vtkMaptkCamera.cxx#L70)

> [...lots more stuff...]

Fine. You Win. You have seriously trashed my motivation to work on this
proposal. Congratulations, it's now on you if we are to get this feature
at all.

I'll be waiting to see the paper you write. I'll even be happy to
critique it (I still absolutely loathe the idea of having to write both
`[*]` *and* `...` to actually use an unpacked tuple, for example; IMO
that's just absurd).

> Oh, and the temporary in this case must be named. Weren't you one of those
> who were saying that naming things is hard

Naming *API* is hard :-). I dub the temporary 't'. Or 't0'. Or whatever;
the names of temporar^Wlocal scratch variables don't matter the way the
names of API types do.

--
Matthew

Vicente J. Botet Escriba

unread,
Jan 19, 2016, 6:08:01 PM1/19/16
to std-pr...@isocpp.org
Le 18/01/2016 22:38, Matthew Woehlke a écrit :
> Okay, after Vicente convinced me I should write this, I present to you
> the start of a paper proposing a '[*]' operator (not pushed yet, and
> just .rst for now; sorry).
Hi, not that I'm against your proposal, but I believe you will need to
justify why this operator that IMHO is almost equivalent to
unpack()/unpacked<T> is more important than one that could replace
ref()/reference_wrapper<T>. The goals for both (ref and unpack) are
similar and the library solution limitations are the same.
Note that all the limitations of the library solution you describe are
already characteristics of reference-wrapper and invoke.

About the "require complicated specification" of a library solution, I
believe that the language solution will not be simple neither.

Would the operator be applicable to rvalue references? universal
references? What would be its meaning?

Could the tuple-like contains reference_wrapper<T> elements? How will
the operator[*] take in account this specific elements?
> The general gist is:
>
> struct Datum { int id; double value; };
>
> auto d1 = Datum{42, 2.7};
> auto d2 = Datum{[*]d1};
> foo([*]d1, [*]d2);
>
> // equivalent to:
> auto d2 = Datum{get<0>(d1), get<1>(d2)};
> foo(get<0>(d1), get<1>(d1), get<0>(d2), get<1>(d2));
>
> (Yes, the first case is silly; it's more interesting if instead of a
> Datum in both cases, the second was a different but layout-compatible
> type. The paper has a somewhat better version of the example.)
>
> As described in the paper, there is at least one use case for this
> language feature that cannot be (easily) accomplished otherwise:
>
> template <type T>
> foo(T const& tuple_like)
> {
> new Bar{next_id(), [*]tuple_like};
> }
>
> If Bar cannot take a T directly (e.g. because it is an external library
> type), but the 'tuple size' of T is not known, it is impossible to write
> a single generic definition of foo() (and hard, or at least very
> annoying, to write specializations).
I guest that when you say that tuple size of T is not known, you mean
that it is not fixed and that it depends on T (tuple_size<T>::value
should give the number.
I recognize that my library proposal doesn't takes in account
constructors, as we can not take its address. We will need a Bar factory.

An alternative to your foo is

template <type ..Ts>
foo(Ts const& ...vs)
{
new Bar{next_id(), vs...};
}

This version could be called using apply

apply(foo, t);

But I agree, this will not be always possible, e.g. if there are 2
tuples. Maybe it is worth changing the example.
> I haven't written a proposed wording yet, and not sure if I will be able
> to until "tuple like" is specified. (BTW, is there an official paper on
> that yet?)
Not, that I know.
> Comments, as always, welcome. Motivating use cases especially welcome :-).
>


BTW, I've started the library proposal paper but it is not ready yet ;-)

I've not a library solution for constructors or returning an aggregate
with elided type.

Respect to use case,

pair<int, S> parse_int(S);
void g(int, int, S);

g(1, [*]parse_int(s));

Maybe OT. It is a pity that we don't have an associated type for an
aggregate initializer {1,"b",3} that could be managed as a tuple-like
type, let me name it initializer_aggregate. Constructing from an
initializer_aggregate could be equivalent to constructing from its
elements.

template <type T>
foo(T const& tuple_like)
{
new Bar{ make_aggreagte_initializer(next_id(), unpack(tuple_like) };
}


Calling a function with a initializer_aggregate could be equivalent to
calling it with its elements.


Vicente


Arthur O'Dwyer

unread,
Jan 20, 2016, 1:50:34 AM1/20/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Tuesday, January 19, 2016 at 12:23:25 PM UTC-8, Matthew Woehlke wrote:
On 2016-01-19 14:15, Nicol Bolas wrote:
> Your mechanism is too simplified to be of *general* use.

I disagree. I can think of immediate examples of unpacking in place (and
some have been given). I would like to see a reasonable example for:

  auto result = make_tuple(func([*]f..., [*]g)...);

...along with a rationale why this merits a language feature.

Aren't you both (Nicol and Matthew) proposing language features? Matthew is proposing `[*]`, and Nicol is proposing `[*]...`.  I don't see how either of those can be implemented as a library-only feature.

The only difference between Nicol's proposal and Matthew's (AFAICT) is whether [*]x "expands in place" into a comma-separated sequence of gets (Matthew's), or whether it evaluates to a parameter pack at the AST level (Nicol's).

If that is in fact the only difference, then of course Nicol's proposal is better. Parameter packs are well-understood, are composable in interesting ways (e.g. `f(g(x)...)` versus `f(g(x...))`), and are going to become even more composable in C++17 (e.g. fold-expressions). A comma-separated sequence is just one possible result of expanding a parameter pack.
Plus, parameter packs have Standard wording already, so you wouldn't need to write any new wording to describe what you (Matthew) mean by "any context where a ``,``-separated sequence of expressions is accepted". (Matthew doesn't really mean that literally anyway, unless he's proposing to allow

    [*]x;

as an expression statement containing std::tuple_size_v<decltype(std::tie([*]x))> - 1 instances of the comma operator.)

Notice that in the previous parenthetical I had to write std::tuple_size_v<decltype(std::tie([*]x))> - 1 to express the size of the "comma-separated sequence" [*]x under Matthew's proposal. I think that's really the simplest thing that would compile! Under Nicol's proposal, the same notion would be expressed as sizeof...([*]x). This illustrates what I mean by the statement "Parameter packs are well-understood." We know how to work with them; we have language tools that have been carefully designed to work with them. Whereas, IMO, "comma-separated sequences" are weird and un-C++thonic.


p.s. If we have genuine promotion of value sequences to parameter packs,
why can't we do this?

  auto&&... g_unpacked = g;
  auto result = make_tuple(func([*]f, g_unpacked)...);

Because you can't have variables of parameter-pack type. Parameter packs aren't first-class citizens in C++. Maybe they should be; but that seems like a distraction from the issue at hand, which is
- should tuples be convertible back into packs, or into some other kind of entity? and
- what should the syntax for that look like?
The questions of "why can't I declare a pack on the stack", or "why can't I init-capture a pack", or "why can't I declare a class A<T...> that has members of types T..." are all good questions, but are completely orthogonal to the two questions above. And they'll still be there for the solving later.

 
> ... The *only part* of your proposal that absolutely needs to be a
> *language* feature is the ability to unpack a tuple into a
> braced-init-list.

I think you're severely understating the usefulness of this case. I
don't feel that being able to unpack into e.g. constructor calls is so
rare as to be so lightly glossed over.

I suspect Nicol was implying that "unpacking into the arguments of a constructor call" can be simulated by "unpacking into the arguments of a function call":
    new (addr) T(pack...)
is exactly equivalent to
    std::experimental::apply(std::allocator<T>::construct, std::allocator<T>{}, addr, pack...);
and creating temporary objects of type T can also be done with constructs such as my::make<T>(pack...), as long as T is moveable.  Richard Smith's P0135 "Guaranteed copy elision through simplified value categories" removes even that "moveable" requirement.
Whereas, there is no library solution for "creating a braced-init-list". Braced-init-lists are special in that they force left-to-right (LTR) evaluation of their contents. You can't do that with a function call. (Although, as previously noted on this list, I wish you could. ;))

> Oh, and the temporary in this case must be named. Weren't you one of those
> who were saying that naming things is hard

Naming *API* is hard :-). I dub the temporary 't'. Or 't0'. Or whatever;
the names of temporar^Wlocal scratch variables don't matter the way the
names of API types do.

Depends on your domain, as always. If your codebase has a lot of deeply nested constructs and compiles with -Wshadow, naming local variables can be challenging. If you're working on a code-generator that generates C++ code, the first time you run into some construct that requires you to implement a

std::string get_temp_variable_name() {
    static int x = 0;
    return "t"+std::to_string(x++);
}

if you're like me you're going to spend at least half an hour tearing your hair out to find any workaround for this $*#$% stupid language feature that requires making up ad-hoc names. Naming things sucks; IMHO half the attraction of "point-free style" is that it eliminates most of the bikeshedding over naming! :)

–Arthur

Matthew Woehlke

unread,
Jan 20, 2016, 10:28:52 AM1/20/16
to std-pr...@isocpp.org
On 2016-01-20 01:50, Arthur O'Dwyer wrote:
> Aren't you both (Nicol and Matthew) proposing language features? Matthew is
> proposing `[*]`, and Nicol is proposing `[*]...`. I don't see how either
> of those can be implemented as a library-only feature.

Yes, to all points. I feel like I've provided better examples, however,
and that the specification of my version is significantly simpler. Maybe
if Nicol will present a specification of his version, I will change my
mind, but it still seems to me that his is more complicated and harder
to use.

> Plus, parameter packs have Standard wording already, so you wouldn't need
> to write any new wording to describe what you (Matthew) mean by *"any
> context where a ``,``-separated sequence of expressions is accepted"*.

I didn't actually intend to. `[*]` is expanded early. It may result in
invalid code, which would then be an error.

> (Matthew doesn't really mean that literally anyway, unless he's proposing
> to allow
>
> [*]x;

The non-complicated version of the proposal would indeed allow this; it
would expand to `get<0>(x), get<1>x, ..., get<N>(x);`, which is valid
code. Silly, but valid.

> Notice that in the previous parenthetical I had to write std::tuple_size_v<decltype(std::tie([*]x))>
> - 1 to express the size of the "comma-separated sequence" [*]x under
> Matthew's proposal. I think that's really the simplest thing that would
> compile!

Uh... why? What's wrong with std::tuple_size_v(x)? Recall that in the
discussions of expanding the notion of "tuple-like", it's generally been
implied that tuple_size would be defined, in addition to get<N>.

> On Tuesday, January 19, 2016 at 12:23:25 PM UTC-8, Matthew Woehlke wrote:
>> p.s. If we have genuine promotion of value sequences to parameter packs,
>> why can't we do this?
>>
>> auto&&... g_unpacked = g;
>> auto result = make_tuple(func([*]f, g_unpacked)...);
>
> Because you can't have variables of parameter-pack type. Parameter packs
> aren't first-class citizens in C++.

I think you missed the point; that is exactly what I meant by "If we
have genuine promotion of value sequences to parameter packs". Note the
"auto..." syntax.

> - should tuples be convertible back into packs, or into some other kind of
> entity?

I'm not proposing "some other kind of entity". I'm proposing an
immediate code (AST?) transform at point of use. Introducing some new
(or even existing) "other kind of entity" is specifically something I
want to *avoid*.

> The questions of "why can't I declare a pack on the stack", or "why can't I
> init-capture a pack", or "why can't I declare a class A<T...> that has
> members of types T..." are all good questions, but are completely
> orthogonal to the two questions above. And they'll still be there for the
> solving later.

But that's somewhat the point... the question is, if we want the ability
as in the above example *anyway*, then what value is left in Nicol's
proposal over mine (besides that it saves a little typing)? This would
make the common case (just want to unpack in place) easy and the
uncommon case possible, vs. making both possible, but also both ugly.

--
Matthew

Matthew Woehlke

unread,
Jan 20, 2016, 10:49:22 AM1/20/16
to std-pr...@isocpp.org
On 2016-01-19 18:08, Vicente J. Botet Escriba wrote:
> Le 18/01/2016 22:38, Matthew Woehlke a écrit :
>> Okay, after Vicente convinced me I should write this, I present to you
>> the start of a paper proposing a '[*]' operator (not pushed yet, and
>> just .rst for now; sorry).
> Hi, not that I'm against your proposal, but I believe you will need to
> justify why this operator that IMHO is almost equivalent to
> unpack()/unpacked<T> is more important than one that could replace
> ref()/reference_wrapper<T>.

Did I not address that directly?

- a std::unpacked is ambiguous; do I want to unpack it *now* (or
"ASAP"), or keep it together for the moment?

- std::unpack can't be used in initializer lists; e.g. this is
impossible: `double x[] = {[*]array_like};`.

...and of course, using unpack is a *lot* more unwieldy. While as has
been repeatedly pointed out, that's not necessarily a rationale, there's
a pretty big difference between:

return std::allocate<Bar>(std::invoke(std::construct<Bar>,
std::unpack(tuple_like))); // or whatever

...and:

return {[*]tuple_like};

> About the "require complicated specification" of a library solution, I
> believe that the language solution will not be simple neither.

That's hard to say without actually implementing it, and in fairness,
I'm maybe not comparing apples to apples. The language feature is pretty
easy to *specify*, however, and because it's a *language* feature,
compilers are allowed to employ any manner of shortcuts and other magic
to implement it efficiently. With a library solution, that, at best,
would require implementing compiler magic syntax to do essentially the
same thing...

> Would the operator be applicable to rvalue references? universal
> references? What would be its meaning?

Uh... yes? Why wouldn't it? It would work much like the RHS of a
range-based for.

range-based for:

_c = <container expression>; // assign to temporary
_e = end(_c)
for (_i = begin(_c); _i != _e; ++_i)
<iterator expression> = *_i;

unpack operator:
_t = <tuple-like value>
get<0>(_t), get<1>(_t), ...

That is, the expression is evaluated into a temporary, and get<N>
operates on the temporary. Just like range-based for evaluates the
container expression into a temporary and operates on that.

> Could the tuple-like contains reference_wrapper<T> elements? How will
> the operator[*] take in account this specific elements?

Exactly as if you'd written `get<N>(tuple_like)` for such an element. I
don't see why this is complicated...

>> As described in the paper, there is at least one use case for this
>> language feature that cannot be (easily) accomplished otherwise:
>>
>> template <type T>
>> foo(T const& tuple_like)
>> {
>> new Bar{next_id(), [*]tuple_like};
>> }
>>
>> If Bar cannot take a T directly (e.g. because it is an external library
>> type), but the 'tuple size' of T is not known, it is impossible to write
>> a single generic definition of foo() (and hard, or at least very
>> annoying, to write specializations).
>
> I guest that when you say that tuple size of T is not known, you mean
> that it is not fixed and that it depends on T (tuple_size<T>::value
> should give the number.

Correct (known to the generic definition of foo<T>()). If it were known,
the definition of foo() could just say `get<0>(tuple_like),
get<1>(tuple_like), ...`. Since it is not known, you would have to
specialize foo<T> for tuple_size<T>.

--
Matthew

Nicol Bolas

unread,
Jan 20, 2016, 1:43:40 PM1/20/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Wednesday, January 20, 2016 at 10:28:52 AM UTC-5, Matthew Woehlke wrote:
On 2016-01-20 01:50, Arthur O'Dwyer wrote:
> Aren't you both (Nicol and Matthew) proposing language features? Matthew is
> proposing `[*]`, and Nicol is proposing `[*]...`.  I don't see how either
> of those can be implemented as a library-only feature.

Yes, to all points. I feel like I've provided better examples, however,
and that the specification of my version is significantly simpler. Maybe
if Nicol will present a specification of his version, I will change my
mind, but it still seems to me that his is more complicated and harder
to use.

I presented a more formal description of the idea back in this post. Is there something in particular missing from that?

It's obviously not a formal specification with standards wording or whatever. But it is the equivalent of the "Proposal" section of your document.

> Notice that in the previous parenthetical I had to write std::tuple_size_v<decltype(std::tie([*]x))>
> - 1 to express the size of the "comma-separated sequence" [*]x under
> Matthew's proposal. I think that's really the simplest thing that would
> compile!

Uh... why? What's wrong with std::tuple_size_v(x)? Recall that in the
discussions of expanding the notion of "tuple-like", it's generally been
implied that tuple_size would be defined, in addition to get<N>.

That there would be some kind of sized extension point for "tuple-like" types is understandable. But using it directly is rather more verbose than `sizeof...([*]x)`.
 
> On Tuesday, January 19, 2016 at 12:23:25 PM UTC-8, Matthew Woehlke wrote:
>> p.s. If we have genuine promotion of value sequences to parameter packs,
>> why can't we do this?
>>
>>   auto&&... g_unpacked = g;
>>   auto result = make_tuple(func([*]f, g_unpacked)...);
>
> Because you can't have variables of parameter-pack type. Parameter packs
> aren't first-class citizens in C++.

I think you missed the point; that is exactly what I meant by "If we
have genuine promotion of value sequences to parameter packs". Note the
"auto..." syntax.

> - should tuples be convertible back into packs, or into some other kind of
> entity?

I'm not proposing "some other kind of entity". I'm proposing an
immediate code (AST?) transform at point of use. Introducing some new
(or even existing) "other kind of entity" is specifically something I
want to *avoid*.

> The questions of "why can't I declare a pack on the stack", or "why can't I
> init-capture a pack", or "why can't I declare a class A<T...> that has
> members of types T..." are all good questions, but are completely
> orthogonal to the two questions above. And they'll still be there for the
> solving later.

But that's somewhat the point... the question is, if we want the ability
as in the above example *anyway*, then what value is left in Nicol's
proposal over mine (besides that it saves a little typing)?

Because however much "we want the ability as in the above example *anyway*", there are no proposals for it. People have talked about it, theorized about it. But nobody has actually taken it to the committee.

More importantly, if you try to do everything my proposal suggests in separate pieces, then you're going to create incompatible APIs.

Let's say that we do what you want. We allow `[*]` to mean "immediately unpack this expression." But after C++17 is in beta, someone proposes the ability to turn tuple-like types into value sequences, which can be unpacked identically to parameter packs. Well, you've already absconded with valuable syntax that could have been used for that. Not only that, since your syntax already covers some of that, someone such as yourself can argue, "well, we already can immediately unpack them, so let's just leave it there and add library functions to serve the needs of larger unpack patterns."

Doing a half-job at fixing a problem makes it that much harder to justify the eventual proper fix. Just look at uniform initialization. It's almost uniform, yet nobody has made even the slightest effort to improve it and fix the not-uniform problems with it. Why?

Because it's "good enough" as is.

It's one thing for a feature to be merely "good enough" due to an oversight. It's another for it to be "good enough" due to an unwillingness to actually fulfill the promise of the feature.

This would
make the common case (just want to unpack in place) easy and the
uncommon case possible, vs. making both possible, but also both ugly.

"Ugly" is a matter of perspective and how used to a feature you are. As for commonality, I can't prove that the many uses for generalized pack creation/expansion number substantially greater than localized tuple expansion.

However, the nice thing about having syntax that serves the uncommon cases well is that we find out something about why those cases were uncommon.

The benefits of C++11 and 14 to template metaprogramming aren't just that you can express more powerful constructs. It's that it is easier to express those constructs. The power of concepts is not that it allows you to do something you couldn't do before; it's that it takes something that was exceedingly difficult to do (`std::enable_if` gymnastics) and turns it into something trivial.

That's one of the benefits of structured binding, after all. One of the big reasons not to use tuple to return multiple values is the painful syntax of retrieving those values. With structured binding, that pain disappears. If you give people easy-to-use MRVs, they will use them.

The same goes here. What might be considered uncommon today may well be uncommon only because it's hard. The moment it becomes easy, we may find ourselves typing `...` a whole lot more ;)

Don't pre-nerf a feature just because you don't think it'll be used as much.

Matthew Woehlke

unread,
Jan 20, 2016, 2:34:11 PM1/20/16
to std-pr...@isocpp.org
On 2016-01-20 10:49, Matthew Woehlke wrote:
> On 2016-01-19 18:08, Vicente J. Botet Escriba wrote:
>> Le 18/01/2016 22:38, Matthew Woehlke a écrit :
>>> Okay, after Vicente convinced me I should write this, I present to you
>>> the start of a paper proposing a '[*]' operator (not pushed yet, and
>>> just .rst for now; sorry).
>> Hi, not that I'm against your proposal, but I believe you will need to
>> justify why this operator that IMHO is almost equivalent to
>> unpack()/unpacked<T> is more important than one that could replace
>> ref()/reference_wrapper<T>.
>
> [...] using unpack is a *lot* more unwieldy. While as has been
> repeatedly pointed out, that's not necessarily a rationale, there's a
> pretty big difference between:
>
> return std::allocate<Bar>(std::invoke(std::construct<Bar>,
> std::unpack(tuple_like))); // or whatever
>
> ...and:
>
> return {[*]tuple_like};

Another example (using real, production code):

// current code
camera->SetCenter(loc[0], loc[1], loc[2]);

// my proposal (add a '...' for Nicol's proposal)
camera->SetCenter([*]loc);

// your proposal
std::invoke(camera.GetPointer(), &vtkCamera::SetCenter,
std::unpack(loc));

Mine or Nicol's will probably win over what we have now (less typing).
Yours... almost certainly won't :-). It's nice for generic code, and
that's about it. Even for simpler examples, it's very unlikely I'd use
your unpack over just doing it longhand in most instances.

--
Matthew

Matthew Woehlke

unread,
Jan 20, 2016, 2:40:06 PM1/20/16
to std-pr...@isocpp.org
On 2016-01-20 13:43, Nicol Bolas wrote:
> On Wednesday, January 20, 2016 at 10:28:52 AM UTC-5, Matthew Woehlke wrote:
>> What's wrong with std::tuple_size_v(x)? Recall that in the
>> discussions of expanding the notion of "tuple-like", it's generally been
>> implied that tuple_size would be defined, in addition to get<N>.
>
> That there would be some kind of sized extension point for "tuple-like"
> types is understandable. But using it directly is rather more verbose than
> `sizeof...([*]x)`.

`tuple_size_v(x)` is "rather more verbose" than `sizeof...(x)`? Only by
3 characters... and anyway I don't understand the relevance, since we're
talking about internal details, and not something that appears in user code.

> "Ugly" is a matter of perspective and how used to a feature you are. As for
> commonality, I can't prove that the many uses for generalized pack
> creation/expansion number substantially greater than localized tuple
> expansion.

Anecdotal, of course, but I can't think that I have *ever* wanted to
invoke a function for each member of a tuple-like. I'm having some
trouble even thinking of cases where I want `func(pack)...` (i.e. with
variadic templates). That suggests that they're rare (and is why I've
asked you to show realistic examples.)

Conversely, I *know* I often want to unpack tuple-likes into function
call arguments. It took barely a minute to turn up a handful of examples
in *real, production code* where I would have used the feature for that
purpose, if I could.

Okay, having said that, I *can* think of one case... can you show how
you would simplify this code?

stream << "begin" << get<0>(x) << get<1>(x)
<< ... << get<N>(x) << "end";

(Rules: 'stream' must be named exactly once, and no fair creating a
helper function. Strong preference to avoid assigning 'stream' to a
scratch variable.)

For bonus points, include `", "` between each item of `x` (okay to
include after the last item also).

--
Matthew

Nevin Liber

unread,
Jan 20, 2016, 3:12:38 PM1/20/16
to std-pr...@isocpp.org
On 20 January 2016 at 13:36, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
Anecdotal, of course, but I can't think that I have *ever* wanted to
invoke a function for each member of a tuple-like.

Comparison, hashing, serialization, streaming just to name a few...
 
That suggests that they're rare

No, it suggests your experience is limited.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

Arthur O'Dwyer

unread,
Jan 20, 2016, 6:13:24 PM1/20/16
to std-pr...@isocpp.org
On Wed, Jan 20, 2016 at 7:28 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
On 2016-01-20 01:50, Arthur O'Dwyer wrote:
> (Matthew doesn't really mean that literally anyway, unless he's proposing
> to allow
>
>     [*]x;

The non-complicated version of the proposal would indeed allow this; it
would expand to `get<0>(x), get<1>x, ..., get<N>(x);`, which is valid
code. Silly, but valid.

Parameter-pack expansion doesn't allow expansion-into-the-comma-operator here. You're proposing to add a new "tuple-like-expansion" that does allow expansion-into-the-comma-operator. That's weirder than just reusing the existing language feature (parameter-pack expansion). Having two things that do almost the same thing but behave subtly differently is bad UI design and bad for the people who have to come up with the normative wording.

> Notice that in the previous parenthetical I had to write std::tuple_size_v<decltype(std::tie([*]x))>
> - 1 to express the size of the "comma-separated sequence" [*]x under
> Matthew's proposal. I think that's really the simplest thing that would
> compile!

Uh... why? What's wrong with std::tuple_size_v(x)? Recall that in the
discussions of expanding the notion of "tuple-like", it's generally been
implied that tuple_size would be defined, in addition to get<N>.

Okay, std::tie([*]x) should just be x; that was my fault. But you still have to write std::tuple_size_v<decltype(x)>. The entity std::tuple_size_v is a variable template, not a constexpr function.

Off-topic but FYI, I'm still a little generally uncomfortable with the idea of "tuple-like type". For example, in C++14 std::array<T,N> has become "tuple-like", even though it has only one template type parameter. If we were to standardize a "typelist" template (e.g. mpl::vector<T...>), would it not be "tuple-like" because you couldn't use std::get<I> on it at runtime? And if I-the-user want to define my own "tuple-like type", how many customization points do I have to specialize, overload, or whatever, before I can be sure that the STL will treat my type as "tuple-like"?
Admittedly we have had the same problems since 2011 with "container-like" types and range-based for loops, and they don't really seem to be tripping anybody up.
 

> On Tuesday, January 19, 2016 at 12:23:25 PM UTC-8, Matthew Woehlke wrote:
>> p.s. If we have genuine promotion of value sequences to parameter packs,
>> why can't we do this?
>>
>>   auto&&... g_unpacked = g;
>>   auto result = make_tuple(func([*]f, g_unpacked)...);
>
> Because you can't have variables of parameter-pack type. Parameter packs
> aren't first-class citizens in C++.

I think you missed the point; that is exactly what I meant by "If we
have genuine promotion of value sequences to parameter packs". Note the
"auto..." syntax.

I probably misunderstood the combination of the "If" (namely: what are we assuming we have?) and the "why can't we" (namely: is that a real question or did you mean it rhetorically as "we definitely can"?).
Nicol's proposal involves a new way to get a parameter-pack into the middle of an expression, but it doesn't change anything about the first-class-citizen-ness of parameter-packs. You still can't have "values" of "type" parameter-pack. Nor can you have named variables of "type" parameter-pack, except for the (previously existing) very special case of function parameters.

That is, I understood your question to mean "Does Nicol's proposal imply that the following should compile?" and/or "Does Matthew's proposal imply that the following should compile?". Both answers were "no".
You may have meant, "If (under some hypothetical(?) future(?) proposal) the following code did compile, then it would definitely compile." If so, then yes, I agree with that statement. ;)


> The questions of "why can't I declare a pack on the stack", or "why can't I
> init-capture a pack", or "why can't I declare a class A<T...> that has
> members of types T..." are all good questions, but are completely
> orthogonal to the two questions above. And they'll still be there for the
> solving later.

But that's somewhat the point... the question is, if we want the ability
as in the above example *anyway*, then what value is left in Nicol's
proposal over mine (besides that it saves a little typing)? This would
make the common case (just want to unpack in place) easy and the
uncommon case possible, vs. making both possible, but also both ugly.

Using "..." to unpack things isn't the ugly part of the proposal IMHO, though!
The ugly part, and the part that's going to get bikeshedded to death, is the choice of [*] as a prefix operator.
Especially since there's another proposal before the Committee (or was, at Kona) suggesting that [*](){ ... } should be the syntax for "capture *this by copy".

Under Nicol's proposal, I now see a further difficulty with mixing [*] and .... Nicol, Matthew, and I have all been writing sample code under Nicol's proposal as if

    f([*]x...)

would naturally Do The Right Thing. But in fact, the operator precedence there is wrong: the postfix ... binds more tightly than the prefix [*]. Under Nicol's proposal, the code would have to be

    f(([*]x)...)

which I now agree is horribly ugly. :)  However, the fix is simple: you just need to change the "unpacking" operator from prefix to postfix. For discussion purposes, I'll nominate postfix ~.

    f(x~...)

That's only one character longer than f([*]x), and arguably more readable.

    template<class Vec>
    auto dotproduct(const Vec& a, const Vec& b)
    {
        return (... + (a~ * b~));  // this is a C++17 fold-expression
    }

Not that I'm claiming above-average readability for that snippet, mind you! :)  But IMHO it's better than

    template<class Vec>
    auto dotproduct(const Vec& a, const Vec& b)
    {
        auto f = [](auto&&... aibi){
            return (... + (aibi.first * aibi.second));
        };
        return f([*]my::tuple_zip(a, b));
    }

Keep in mind that I failed the simplicity test last time I tried to write sample code according to Matthew's proposal, so my apologies if I've missed something simpler again.  (Something simpler that still involves unpacking, I mean!  My code above also relies on a made-up my::tuple_zip. If we postulate a made-up my::tuple_foldl, though, the whole problem collapses away and we don't need any language features at all:

    template<class Vec>
    auto dotproduct(const Vec& a, const Vec& b)
    {
        auto f = [](auto&& accumulator, auto&& ab) {
            return accumulator + (ab.first * ab.second);
        };
        return my::tuple_foldl(f, 0, my::tuple_zip(a, b)));
    }

–Arthur

Arthur O'Dwyer

unread,
Jan 20, 2016, 6:41:58 PM1/20/16
to std-pr...@isocpp.org
On Wed, Jan 20, 2016 at 11:36 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:

Okay, having said that, I *can* think of one case... can you show how
you would simplify this code?

  stream << "begin" << get<0>(x) << get<1>(x)
         << ... << get<N>(x) << "end";

(Rules: 'stream' must be named exactly once, and no fair creating a
helper function. Strong preference to avoid assigning 'stream' to a
scratch variable.)

Sure!

    // Nicol's syntax
    (stream << "begin" << ... << [*]x) << "end";
    // Arthur's syntax
    (stream << "begin" << ... << x~) << "end";
 
For bonus points, include `", "` between each item of `x` (okay to
include after the last item also).

This is significantly harder!  I think it would have to look something like this:

template<class F>
auto operator<< (std::ostream& out, F&& f) -> decltype(f(out))  // SFINAE
{
    return f(out);  // maybe C++17 should provide this overload already
}

template<class TupleLike>
void print_to_stream(std::ostream& stream, const TupleLike& x)
{
    auto commaize = [](auto&& x) {
        return [&x](std::ostream& out) -> std::ostream& { return out << x << ", "; };
    };

    (stream << "begin, " << ... << commaize(x~)) << "end";
}

Messy, but not positively horrible. What would it look like in your (Matthew's) proposal?

–Arthur

T. C.

unread,
Jan 20, 2016, 7:03:45 PM1/20/16