RFC: Unpacking tuples to value sequences

446 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
to ISO C++ Standard - Future Proposals
If you can write stream twice, then a comma fold will do:

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

Also, I'm fairly sure you need to parenthesize stream << "begin, ". The grammar only allows a cast-expression there.

Arthur O'Dwyer

unread,
Jan 20, 2016, 7:37:09 PM1/20/16
to std-pr...@isocpp.org
On Wed, Jan 20, 2016 at 4:03 PM, T. C. <rs2...@gmail.com> wrote:
On Wednesday, January 20, 2016 at 6:41:58 PM UTC-5, Arthur O'Dwyer wrote:

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";
}


If you can write stream twice, then a comma fold will do:

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

Also, I'm fairly sure you need to parenthesize stream << "begin, ". The grammar only allows a cast-expression there.

I bet you're right. :)  The above compiles with GCC 6.0 on Wandbox (if you replace x~ with std::get<I>(x) of course) and fails for unrelated reasons with Clang 3.9. But I can totally believe that GCC 6.0 has bugs and/or helpful extensions in this area.

Personally, I was shocked that

    return (a * b) + ...;

failed to compile on either Clang or GCC — and with a completely unhelpful syntax error! It took me tens of minutes to discover what I was doing wrong (mostly spent trying to figure out if these compilers claimed to support fold-expressions at all).

Is there a grammatical reason to require parentheses around every single fold-expression, or was it just someone's idea of being "helpful"?

–Arthur

Matthew Woehlke

unread,
Jan 21, 2016, 10:09:11 AM1/21/16
to std-pr...@isocpp.org
On 2016-01-20 19:03, T. C. wrote:
> If you can write stream twice,

You can't. The specific use case in which I'd want to do this is with
QDebug:

qDebug() << "stuff" << ...

The comma isn't terribly important, because QDebug will add spaces.
However, it will add blank lines which are NOT wanted if qDebug() is
called multiple times. Nor is using a scratch variable really an option,
because that would require adding an explicit scope to make the QDebug
instance destruct at the right place.

--
Matthew

Matthew Woehlke

unread,
Jan 21, 2016, 10:40:35 AM1/21/16
to std-pr...@isocpp.org
On 2016-01-20 18:13, Arthur O'Dwyer wrote:
> 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.

So? The intent of "tuple-like" is that it is a sequence of a fixed
number of values. There is nothing saying that those values must be of
different types.

Most of the examples where I want something to be tuple-like are
actually arrays (typically, "vectors" in the linear algebra sense), e.g.
QPoint, Eigen::Vector<...>, etc.

> 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?

Yeees.... also because tuple_size wouldn't (couldn't) be constexpr. IOW,
it is not "tuple-like" if it is not fixed(-at-compile-time)-size.

This is of course why std::array is tuple-like and std::vector is not.

> 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"?

That's an active question; see
https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/_dz9X2HVWl4.
I've repeatedly expressed the preference that the answer is 'get<N>' and
nothing else. Another opinion is 'get<N>' and 'tuple_size'. I don't
believe anyone is proposing additional requirements beyond those.

> On 2016-01-20 10:28, Matthew Woehlke wrote:
>> On 2016-01-20 01:50, Arthur O'Dwyer wrote:
>>> 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.
>
> [...]
> 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".

Ah, yes. I meant, "If <other feature> is something we also want anyway,
do we not have the same capabilities with <my version of the proposal>
and <other feature>? Would that not make <Nicol's version of the
proposal> unnecessary?"

I think Nicol addressed this adequately, so I won't rehash beyond
clarification of the original intended meaning.

> 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
> <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0018r0.html>
> before the Committee (or was, at Kona) suggesting that [*](){ ... } should
> be the syntax for "capture *this by copy".

I'm open to other (good) ideas... one reason I really like `[*]` however
is because it becomes natural to further extend it to slicing, e.g.
`[1]`, `[:3]`, `[2:4]`, `[1,2:4,5,8:]`, and so forth.

I think slicing is important; already with assignment unpacking, we have
the problem of how to deal with unwanted values. If we have slicing, we
can just write:

auto {a, b} = [2,5]tuple_like;

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

Per above, `[*]` wasn't chosen lightly. The use of `[]`s suggest the
indexing operator, which therefore implies something being extracted
from a container (and are extensible to multiple specifications of
"something"). The use of `*` is taken directly from Python, and per *my*
proposal, means the exact same thing. (Nicol's would make it... similar,
but less "exactly the same".)

`~` "comes out of nowhere", if you will... there is no existing use in
any language that would lead one to associate it with unpacking.

> 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)));
> }

Isn't that the same as `std::experimental::apply`? Yes, there are some
instances where a language feature would be just a shortcut. The places
where it's more useful, or *necessary* (initializer lists), have been
previously discussed.

--
Matthew

Thiago Macieira

unread,
Jan 21, 2016, 11:40:13 AM1/21/16
to std-pr...@isocpp.org
On Thursday 21 January 2016 10:09:02 Matthew Woehlke wrote:
> Nor is using a scratch variable really an option,
> because that would require adding an explicit scope to make the QDebug
> instance destruct at the right place.

That's a choice and you can always choose differently.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Arthur O'Dwyer

unread,
Jan 23, 2016, 2:48:58 PM1/23/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Thursday, January 21, 2016 at 7:40:35 AM UTC-8, Matthew Woehlke wrote:
On 2016-01-20 18:13, Arthur O'Dwyer wrote:
> 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.

So? The intent of "tuple-like" is that it is a sequence of a fixed
number of values.
[...] 
> 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?

Yeees.... also because tuple_size wouldn't (couldn't) be constexpr. IOW,
it is not "tuple-like" if it is not fixed(-at-compile-time)-size.

Presumably tuple_size would be defined for mpl::vector the same way it is for std::tuple.

template< class... Types >

class tuple_size< mpl::vector<Types...> >

 : public std::integral_constant<std::size_t, sizeof...(Types)> { };
 
This is of course why std::array is tuple-like and std::vector is not.

mpl::vector is not std::vector, though.


> 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
> <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0018r0.html>
> before the Committee (or was, at Kona) suggesting that [*](){ ... } should
> be the syntax for "capture *this by copy".

I'm open to other (good) ideas... one reason I really like `[*]` however
is because it becomes natural to further extend it to slicing, e.g.
`[1]`, `[:3]`, `[2:4]`, `[1,2:4,5,8:]`, and so forth.

I think slicing is important; already with assignment unpacking, we have
the problem of how to deal with unwanted values. If we have slicing, we
can just write:

  auto {a, b} = [2,5]tuple_like;

This seems like a non-starter because of the ambiguity of

    int x = 1;
    int y = 2;
    auto {a, b} = [x,y](std::array<int,3>(z));
    auto {a, b} = [x,y](std::array<int,3> z){ return z }();

Notice how many tokens deep you have to get into that expression before you can figure out whether it's a lambda or a prefix-slice-expression.
The "prefix square brackets" syntax has been fully co-opted by lambdas at this point; you won't be able to use it for a second major feature unless you're really careful.


Per above, `[*]` wasn't chosen lightly. The use of `[]`s suggest the
indexing operator, which therefore implies something being extracted
from a container (and are extensible to multiple specifications of
"something"). The use of `*` is taken directly from Python, and per *my*
proposal, means the exact same thing. (Nicol's would make it... similar,
but less "exactly the same".)

Yes, I recognized prefix * as being a Pythonism. Matching Python would be nice. But, because prefix * already has a different meaning in C++, we've got to change the syntax at least a little bit. We also have to change it to fit in with the existing features of the language, such as pack-expansion, fold-expressions, and so on.
 
`~` "comes out of nowhere", if you will... there is no existing use in
any language that would lead one to associate it with unpacking.

Right; I picked it because it was obviously available (whereas other constructs such as "postfix :" weren't obviously available). However, I could teach it pretty easily. "If you want to unpack a tuple into its first element, second ~, third ~, etc., you use postfix ~."
See http://www.merriam-webster.com/dictionary/swung%20dash — and for those who haven't heard of this usage in English, it's still not too far a step from "postfix dot-dot-dot" to "postfix squiggle".
But again, the syntax will always be subject to bikeshedding. I'm not too worried about that part. x~ is just what looks nicest for writing up examples.
 

> 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)));
>     }

Isn't that the same as `std::experimental::apply`?

No, not at all. foldl is one of the functional primitives we'd want a tuple-functional-programming library to provide, along with tuple_map, tuple_foldr, tuple_filter, tuple_zip... and probably lots more. But if we get tuple-unpacking into the language, then we can use pack-expressions to simulate all of those — more readably and more efficiently.

–Arthur

Matthew Woehlke

unread,
Jan 25, 2016, 9:26:44 AM1/25/16
to std-pr...@isocpp.org
On 2016-01-23 14:48, Arthur O'Dwyer wrote:
> On Thursday, January 21, 2016 at 7:40:35 AM UTC-8, Matthew Woehlke wrote:
>>
>> On 2016-01-20 18:13, Arthur O'Dwyer wrote:
>>> 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.
>>
>> So? The intent of "tuple-like" is that it is a sequence of a fixed
>> number of values.
>
> [...]
>
>>> 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?
>>
>> Yeees.... also because tuple_size wouldn't (couldn't) be constexpr. IOW,
>> it is not "tuple-like" if it is not fixed(-at-compile-time)-size.
>
> Presumably tuple_size would be defined for mpl::vector the same way it is
> for std::tuple.

std::tuple *is fixed size*. I am not familiar with mpl::vector, but
looking at
http://www.boost.org/doc/libs/1_59_0/libs/mpl/doc/refmanual/vector.html,
it certainly does not appear to be fixed size.

Let me rephrase that: tuple_size *must be constexpr*. Unpacking code is
generated at compile time, which requires that the size be *constant*
and *known at compile time*, i.e. constexpr.

Unless I am completely misreading the doc, I don't see how that could be
true for mpl::vector.

Am I missing something?

>> This is of course why std::array is tuple-like and std::vector is not.
>
> mpl::vector is not std::vector, though.

They both have *dynamic size*.

>> I'm open to other (good) ideas... one reason I really like `[*]` however
>> is because it becomes natural to further extend it to slicing, e.g.
>> `[1]`, `[:3]`, `[2:4]`, `[1,2:4,5,8:]`, and so forth.
>>
>> I think slicing is important; already with assignment unpacking, we have
>> the problem of how to deal with unwanted values. If we have slicing, we
>> can just write:
>>
>> auto {a, b} = [2,5]tuple_like;
>
> This seems like a non-starter because of the ambiguity of
>
> int x = 1;
> int y = 2;
> auto {a, b} = [x,y](std::array<int,3>(z));
> auto {a, b} = [x,y](std::array<int,3> z){ return z }();
>
> Notice how many tokens deep you have to get into that expression before you
> can figure out whether it's a lambda or a prefix-slice-expression.

I see that it took two characters:

- '[': don't know yet
- 'x': not an integer literal -> not a slice expression

Slicing (at least this form) would be permitted only with integer
literals. Since integer literals are not valid capture expressions, it
takes exactly two tokens, every time.

(Okay, we should probably allow template parameters and constexpr
expressions as slice arguments, but even those are fairly distinctive.)

For reasons similar to why mpl::vector is not tuple-like, slicing cannot
take non-constexpr indices.

>> Per above, `[*]` wasn't chosen lightly. The use of `[]`s suggest the
>> indexing operator, which therefore implies something being extracted
>> from a container (and are extensible to multiple specifications of
>> "something"). The use of `*` is taken directly from Python, and per *my*
>> proposal, means the exact same thing. (Nicol's would make it... similar,
>> but less "exactly the same".)
>
> Yes, I recognized prefix * as being a Pythonism. Matching Python *would* be
> nice. But, because prefix * already has a different meaning in C++, we've
> got to change the syntax at least a little bit.

What about `[:]`? Again, looking ahead to slicing, this can be seen as
an unbounded slice. (In fact, in Python, that's exactly what it is...)

Agreed it's somewhat bikeshedding, but I still have a fairly strong
preference for a syntax that will allow slicing.

--
Matthew

Ville Voutilainen

unread,
Jan 25, 2016, 10:13:33 AM1/25/16
to ISO C++ Standard - Future Proposals
On 25 January 2016 at 16:26, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>> Presumably tuple_size would be defined for mpl::vector the same way it is
>> for std::tuple.
>
> std::tuple *is fixed size*. I am not familiar with mpl::vector, but
> looking at
> http://www.boost.org/doc/libs/1_59_0/libs/mpl/doc/refmanual/vector.html,
> it certainly does not appear to be fixed size.

Zomg wtf, mpl::vector is not only fixed size, but also immutable.
You can't change its size, the way append works in mpl is that it
gives you a completely new type, that is, it's completely pure with
no side-effects.

>> mpl::vector is not std::vector, though.
> They both have *dynamic size*.

Nonsense.

Matthew Woehlke

unread,
Jan 25, 2016, 10:32:48 AM1/25/16
to std-pr...@isocpp.org
On 2016-01-25 10:13, Ville Voutilainen wrote:
> On 25 January 2016 at 16:26, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>>> Presumably tuple_size would be defined for mpl::vector the same way it is
>>> for std::tuple.
>>
>> std::tuple *is fixed size*. I am not familiar with mpl::vector, but
>> looking at
>> http://www.boost.org/doc/libs/1_59_0/libs/mpl/doc/refmanual/vector.html,
>> it certainly does not appear to be fixed size.
>
> Zomg wtf, mpl::vector is not only fixed size, but also immutable.
> You can't change its size, the way append works in mpl is that it
> gives you a completely new type, that is, it's completely pure with
> no side-effects.

Bah. Okay, that's what I get for skimming the doc :-). (Y'know, you
could be polite and reply to the "am I missing something" part of my
previous post...)

But if that's the case, then what's the problem here? Arthur originally
said:

On 2016-01-20 18:13, Arthur O'Dwyer wrote:
> would [mpl::vector] *not* be "tuple-like" because you couldn't use
> std::get<I> on it at runtime?

...but then, I don't understand why that would be true?

Back to that question, yes, the idea as I understand it is that
"tuple-like" <=> "can use get<N>". Why is that an issue?

--
Matthew

Ville Voutilainen

unread,
Jan 25, 2016, 11:25:57 AM1/25/16
to ISO C++ Standard - Future Proposals
On 25 January 2016 at 17:32, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> But if that's the case, then what's the problem here? Arthur originally
> said:
>
> On 2016-01-20 18:13, Arthur O'Dwyer wrote:
>> would [mpl::vector] *not* be "tuple-like" because you couldn't use
>> std::get<I> on it at runtime?
>
> ...but then, I don't understand why that would be true?
>
> Back to that question, yes, the idea as I understand it is that
> "tuple-like" <=> "can use get<N>". Why is that an issue?


mpl::vector is a container of types, not values. get<N> is a value-getter.

Matthew Woehlke

unread,
Jan 25, 2016, 12:20:15 PM1/25/16
to std-pr...@isocpp.org
Ah. Um... how would you then *use* an mpl::vector in any context that
you would use a tuple-like? A std::tuple is certainly a value
collection. By extension, a tuple-like is also a value collection.

So... different premise, but same question; is there a problem here (and
if so, why)?

--
Matthew

Ville Voutilainen

unread,
Jan 25, 2016, 12:25:58 PM1/25/16
to ISO C++ Standard - Future Proposals
If your question is whether it's a problem that mpl::vector isn't
tuple-like, I would
be inclined to say that the answer is no. :)

Arthur O'Dwyer

unread,
Jan 25, 2016, 2:37:53 PM1/25/16
to std-pr...@isocpp.org
On Mon, Jan 25, 2016 at 9:20 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
On 2016-01-25 11:25, Ville Voutilainen wrote:
> On 25 January 2016 at 17:32, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>> On 2016-01-20 18:13, Arthur O'Dwyer wrote:
>>> would [mpl::vector] *not* be "tuple-like" because you couldn't use
>>> std::get<I> on it at runtime?
>>
>> ...but then, I don't understand why that would be true?
>>
>> Back to that question, yes, the idea as I understand it is that
>> "tuple-like" <=> "can use get<N>". Why is that an issue?
>
> mpl::vector is a container of types, not values. get<N> is a value-getter.

Ah. Um... how would you then *use* an mpl::vector in any context that
you would use a tuple-like? A std::tuple is certainly a value
collection. By extension, a tuple-like is also a value collection.

As I explained in my original post, it seems obvious that we'd define

template< class... Types >

class tuple_size< mpl::vector<Types...> >

 : public std::integral_constant<std::size_t, sizeof...(Types)> { };

and also

// recursive case
template< std::size_t I, class Head, class... Tail >
struct tuple_element<I, mpl::vector<Head, Tail...>>
    : std::tuple_element<I-1, mpl::vector<Tail...>> { };

// base case
template< class Head, class... Tail >
struct tuple_element<0, mpl::vector<Head, Tail...>> {
   typedef Head type
};

The "Sprout" C++14 library includes these definitions today.

However, there's no such "obvious" definition for std::get.
The most plausible one would be

// please stifle all irrelevant comments re: overload sets and CV-qualifiers
templatestd::size_t I, class... Types >

auto get(const mpl::vector<Types...>&)

   -> std::add_rvalue_reference_t<tuple_element_t<I,mpl::vector<Types...>>>;

// deliberately left with no implementation, a la std::declval

So what we're left with is: mpl::vector is "not tuple-like", but it does implement tuple_size_v and tuple_element_t.
The only part of the "tuple-like" contract that it fails to have an obvious implementation for is the one part of the "tuple-like" contract whose name doesn't involve the word "tuple".
That is why the concept of "tuple-like" bothers me.

–Arthur

Matthew Woehlke

unread,
Jan 25, 2016, 2:58:52 PM1/25/16
to std-pr...@isocpp.org
On 2016-01-25 14:37, Arthur O'Dwyer wrote:
> However, there's no such "obvious" definition for [mpl::vector] std::get.
>
> So what we're left with is: mpl::vector is "not tuple-like", but it *does*
> implement tuple_size_v and tuple_element_t.
> The only part of the "tuple-like" contract that it fails to have an obvious
> implementation for is the one part of the "tuple-like" contract *whose name
> doesn't involve the word "tuple"*.
> *That* is why the concept of "tuple-like" bothers me.

LOL :-). (Note: not at you, at the observed absurdity of the situation¹.)

Thanks for explaining; I understand now.

Per my earlier comment, I don't see how you would use an mpl::vector in
most of the contexts in which you care about something being
"tuple-like" (i.e. unpacking, conversion to/from std::tuple). Ergo,
while I understand (and to an extent agree with) your concern now, I'm
also thinking that it may just be one of those unfortunate facts of life.

I think the problem is that we have a few related but
not-quite-identical concepts, all of which are being called "tuple" (and
all of which can make a reasonable argument that "tuple is the correct
term, darn it").

I am open to suggestions? (Maybe we just avoid using the actual phrase
"tuple-like" in the standard?)

(¹ Interesting and at-least-partly applicable factoid: according to
Scott Adams, laughter is sometimes a tell that you have just changed
your mind on a subject.)

--
Matthew

Nicol Bolas

unread,
Jan 25, 2016, 4:23:49 PM1/25/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Monday, January 25, 2016 at 2:58:52 PM UTC-5, Matthew Woehlke wrote:
On 2016-01-25 14:37, Arthur O'Dwyer wrote:
> However, there's no such "obvious" definition for [mpl::vector] std::get.
>
> So what we're left with is: mpl::vector is "not tuple-like", but it *does*
> implement tuple_size_v and tuple_element_t.
> The only part of the "tuple-like" contract that it fails to have an obvious
> implementation for is the one part of the "tuple-like" contract *whose name
> doesn't involve the word "tuple"*.
> *That* is why the concept of "tuple-like" bothers me.

LOL :-). (Note: not at you, at the observed absurdity of the situation¹.)

Thanks for explaining; I understand now.

Per my earlier comment, I don't see how you would use an mpl::vector in
most of the contexts in which you care about something being
"tuple-like" (i.e. unpacking, conversion to/from std::tuple).

I made the analogy that a tuple-like type is analogous to a non-type template parameter pack or a function parameter pack. Therefore, an `mpl::vector` is like a type template parameter pack. So:

template<typename Typelist, typename ...Ts>
void func(Ts ... ts)
{
 
return make_tuple(Typelist~(std::forward<Ts>(ts))...);
}

Transforms the `ts` parameters in accord with the `mpl::vector` typelist.

This would also work with a tuple parameter:

template<typename Typelist>
void func(tuple<whatever> ts)
{
 
return make_tuple(Typelist~(ts~)...);
}

Now, I'm not saying we ought to do that. But that's the general idea.
 
Ergo,
while I understand (and to an extent agree with) your concern now, I'm
also thinking that it may just be one of those unfortunate facts of life.

I think the problem is that we have a few related but
not-quite-identical concepts, all of which are being called "tuple" (and
all of which can make a reasonable argument that "tuple is the correct
term, darn it").

I am open to suggestions? (Maybe we just avoid using the actual phrase
"tuple-like" in the standard?)

There needs to be a recognition between what is a tuple-like type and what is a tuple-like value.

You can access the number of elements in a tuple-like type, and you can access the types of each of those elements. But types obviously don't have values, so that is a third operation only applicable to values of tuple-like types.

Arthur O'Dwyer

unread,
Jan 29, 2016, 2:21:00 PM1/29/16
to std-pr...@isocpp.org, mwoehlk...@gmail.com
On Mon, Jan 25, 2016 at 1:23 PM, Nicol Bolas <jmck...@gmail.com> wrote:

I made the analogy that a tuple-like type is analogous to a non-type template parameter pack or a function parameter pack. Therefore, an `mpl::vector` is like a type template parameter pack. So:

template<typename Typelist, typename ...Ts>
void func(Ts ... ts)
{
 
return make_tuple(Typelist~(std::forward<Ts>(ts))...);
}

Transforms the `ts` parameters in accord with the `mpl::vector` typelist.

(Using the "postfix twiddle" notation for de-tupling, I see. I'm flattered. :))

IIUC, you're proposing that
- postfix-twiddle on a value V expands to a pack of std::get<i>(V)
- postfix-twiddle on a type T expands to a pack of std::tuple_element_t<i,T>
So, std::tuple<A,B,C> would be both tuple-like and typelist-like, because it supports both of the above operations; but mpl::vector<A,B,C> (without std::get) would be only typelist-like; is that right?

That makes sense.

There needs to be a recognition between what is a tuple-like type and what is a tuple-like value.

You can access the number of elements in a tuple-like type, and you can access the types of each of those elements. But types obviously don't have values, so that is a third operation only applicable to values of tuple-like types.

I tend to disagree with the above statement, because I tend to disagree with any statement that treats "operations on types" and "operations on values" as fundamentally different, ever since I saw Louis Dionne speak for the first time. :)
As of C++11, we have decltype(E) — which takes the value E and produces its type — and we have std::declval<T>() — which takes a type T and produces a value of that type. And they compose, so I can do a bunch of "value-space" computations and then take the decltype of the resulting expression. Any construct that gets in the way of my doing that is objectionable.
In other words, rather than saying that mpl::vector<Ts...> doesn't have a value and so std::get<I>(mplvec) should be ill-formed, I would prefer we simply say that std::get<I>(mplvec) doesn't have a value — but it should still have a type!
In short, I'd strongly prefer that every "typelist-like" type also be "tuple-like", at least from the point of view of the type system, even if that means leaving some "tuple-like operations" declared-but-not-defined.


Having said all that, this "postfix twiddle means std::get<> (and/or std::tuple_element_t<>)" thing still feels too ad-hoc to me. If we could come up with a nice syntax for it, I'd prefer to have the std::get<> written out and just build a simple syntax for getting a parameter-pack of integer indices.  I have no idea what that syntax would look like, though, or how it would end up remotely as clean as this seductive example:

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

The only thing I can think to do is to introduce first-class parameter packs, and write

    template<size_t I, class... Ts>
    auto getp(const std::tuple<Ts...>&) -> Ts...;  // "return a pack"

    template<class Vec>
    auto dotproduct(const Vec& a, const Vec& b)
    {
        return (... + (getp(a) * getp(b)));
    }

But first-class parameter packs seem like a nightmare to specify, don't they?
Plus, at that point we're definitely turning C++ into a vector language, in which "packs of values" are first-class citizens and packwise (i.e. vector) arithmetic is built into the language; and maybe some of the folks who actually do vectorization stuff ought to weigh in and say "this is how we want vector operations to work, for performance or usability reasons."

–Arthur

Matthew Woehlke

unread,
Jan 29, 2016, 4:04:00 PM1/29/16
to std-pr...@isocpp.org
On 2016-01-29 14:20, Arthur O'Dwyer wrote:
> Having said all that, this "postfix twiddle means std::get<> (and/or
> std::tuple_element_t<>)" thing still feels too ad-hoc to me. If we
> could come up with a nice syntax for it, I'd prefer to have the
> std::get<> written out and just build a simple syntax for getting a
> parameter-pack of *integer indices*.

Ugh, please no :-). I'm already not happy about writing:

auto t = get_tuple_like();
foo(t~...); // or whatever; I still don't like '~'

If I have to write:

foo(get<MAGIC...>(t));

...instead (for presumably some not-very-short spelling of "MAGIC"
likely involving a `decltype(t)`), I am not going to be happy. And I am
also going to think we have entirely missed the point.

--
Matthew

Nicol Bolas

unread,
Jan 29, 2016, 4:08:27 PM1/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Friday, January 29, 2016 at 2:21:00 PM UTC-5, Arthur O'Dwyer wrote:
On Mon, Jan 25, 2016 at 1:23 PM, Nicol Bolas <jmck...@gmail.com> wrote:

There needs to be a recognition between what is a tuple-like type and what is a tuple-like value.

You can access the number of elements in a tuple-like type, and you can access the types of each of those elements. But types obviously don't have values, so that is a third operation only applicable to values of tuple-like types.

I tend to disagree with the above statement, because I tend to disagree with any statement that treats "operations on types" and "operations on values" as fundamentally different, ever since I saw Louis Dionne speak for the first time. :)
As of C++11, we have decltype(E) — which takes the value E and produces its type — and we have std::declval<T>() — which takes a type T and produces a value of that type. And they compose, so I can do a bunch of "value-space" computations and then take the decltype of the resulting expression. Any construct that gets in the way of my doing that is objectionable.
In other words, rather than saying that mpl::vector<Ts...> doesn't have a value and so std::get<I>(mplvec) should be ill-formed, I would prefer we simply say that std::get<I>(mplvec) doesn't have a value — but it should still have a type!

You can want whatever you want, but the fact is that in C++, types and values are different things. And we have different mechanisms for dealing with each.

Functions take values and return them. Template metafunctions take types and can evaluate to types. `declval` doesn't really generate a value in any meaningful way, since it can only exist in a non-evaluated context. And while `decltype` does evaluate to a type, that type is not based on a value, since the expression itself is never actually evaluated.

Maybe sometimes these things will be equivalent someday. But until then, C++ is what it is. Values and types are different, and we shouldn't pretend that they're not.

In short, I'd strongly prefer that every "typelist-like" type also be "tuple-like", at least from the point of view of the type system, even if that means leaving some "tuple-like operations" declared-but-not-defined.

I dislike things that are "declared but not defined". The concept is based on various minutiae of C++'s declaration and definition syntax that people shouldn't have to know.

We added `=delete` syntax so that we wouldn't have to rely on such things as much. Similarly, concepts includes a whole mechanism that avoids having to deal with `declval` and a host of other "declared but not defined" constructs associated with `enable_if`.

Also, I rather suspect that `tuple_element_t<Type, I>` takes less time to compile than `decltype(get<I>(declval<Type>()))`. And it's a heck of a lot easier to understand and implement for the user.

Having said all that, this "postfix twiddle means std::get<> (and/or std::tuple_element_t<>)" thing still feels too ad-hoc to me.

It's no more ad-hoc than range-based for using `std::begin` and `std::end`. You have to have some customization point for user-defined types.

If we could come up with a nice syntax for it, I'd prefer to have the std::get<> written out and just build a simple syntax for getting a parameter-pack of integer indices.  I have no idea what that syntax would look like, though, or how it would end up remotely as clean as this seductive example:

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


That example seems to be missing what the return value is supposed to be. Presumably it's some `Vec` object, but you don't use the typename anywhere.

The only thing I can think to do is to introduce first-class parameter packs, and write

    template<size_t I, class... Ts>
    auto getp(const std::tuple<Ts...>&) -> Ts...;  // "return a pack"

    template<class Vec>
    auto dotproduct(const Vec& a, const Vec& b)
    {
        return (... + (getp(a) * getp(b)));
    }

But first-class parameter packs seem like a nightmare to specify, don't they?

Um, how exactly is that easier than the user implementing the appropriate interfaces? It certainly isn't easier for the reader to understand.

Arthur O'Dwyer

unread,
Jan 29, 2016, 8:59:22 PM1/29/16
to std-pr...@isocpp.org
On Fri, Jan 29, 2016 at 1:08 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Friday, January 29, 2016 at 2:21:00 PM UTC-5, Arthur O'Dwyer wrote:

As of C++11, we have decltype(E) — which takes the value E and produces its type — and we have std::declval<T>() — which takes a type T and produces a value of that type. And they compose, so I can do a bunch of "value-space" computations and then take the decltype of the resulting expression. Any construct that gets in the way of my doing that is objectionable.
In other words, rather than saying that mpl::vector<Ts...> doesn't have a value and so std::get<I>(mplvec) should be ill-formed, I would prefer we simply say that std::get<I>(mplvec) doesn't have a value — but it should still have a type!

You can want whatever you want, but the fact is that in C++, types and values are different things. And we have different mechanisms for dealing with each.

Functions take values and return them. Template metafunctions take types and can evaluate to types. `declval` doesn't really generate a value in any meaningful way, since it can only exist in a non-evaluated context. And while `decltype` does evaluate to a type, that type is not based on a value, since the expression itself is never actually evaluated.

Maybe sometimes these things will be equivalent someday. But until then, C++ is what it is. Values and types are different, and we shouldn't pretend that they're not.

That's one point of view, but I tend to think that it's outdated (as of C++11). We don't need template metafunctions anymore (well, not so many of them), now that we have constexpr and heterogeneous function templates at our disposal. For example, where in C++03 we did often have to write template metafunctions such as

template<class T, class U>
struct result_of_plus {
    typedef __typeof(T()+U()) type;
};

struct plus {
    template<class T, class U>
    typename result_of_plus<T,U>::type operator(T t, U u) { return t + u; }
};

as of C++14 we no longer need to write any of that metafunction machinery; we can use decltype to get the compiler to do the meta-lifting on our behalf, and all we need to worry about are expressions, which generally have both a type and a value.

struct plus {
    template<class T, class U>
    auto operator(T t, U u) -> decltype(t + u) { return t + u; }
};

(and we don't even need the -> decltype stuff unless we want SFINAE!)
Values and types are different, but they are still intimately related (for example, every value has a type), and while we can use different mechanisms to deal with each, we no longer need to.


Having said all that, this "postfix twiddle means std::get<> (and/or std::tuple_element_t<>)" thing still feels too ad-hoc to me.

It's no more ad-hoc than range-based for using `std::begin` and `std::end`. You have to have some customization point for user-defined types.

Agreed, I think, grudgingly, reluctantly, in practice.  In other words, I agree that's the current state of the world, but I don't like it. It feels much too ad-hoc. begin and end "work" as well as they do because they had a couple of decades of implementation experience behind them before people started making up syntactic sugar for them. Right now we have maybe 5 years of experience using std::get and probably even less with std::tuple_element_t. Are those exactly the primitives we want to syntactic-sugar up, or will we regret the decision in another 5 years when we realize "how tuples should have been done"?

I mean, imagine if instead of iostreams using operator overloading, we had gotten a syntactic sugar where << in certain contexts expanded to a call to std::printf(). Makes sense, right? But it would have been horrible for the flexibility and symmetry of the language as a whole. And even iostreams-as-is suck!  Basically I'm worried that postfix-twiddle and tuples-as-is are in an analogous situation right now. We might not want to immortalize std::get<I> in language syntax.

 
If we could come up with a nice syntax for it, I'd prefer to have the std::get<> written out and just build a simple syntax for getting a parameter-pack of integer indices.  I have no idea what that syntax would look like, though, or how it would end up remotely as clean as this seductive example:

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


That example seems to be missing what the return value is supposed to be. Presumably it's some `Vec` object, but you don't use the typename anywhere.

The return type is exactly decltype((... + (a~ * b~))). Starting in C++14, we don't have to write it out; auto suffices for return type deduction.
For example, if Vec is std::tuple<int, double, char>, then decltype((... + (a~ * b~))) is double.


The only thing I can think to do is to introduce first-class parameter packs, and write

    template<size_t I, class... Ts>
    auto getp(const std::tuple<Ts...>&) -> Ts...;  // "return a pack"

    template<class Vec>
    auto dotproduct(const Vec& a, const Vec& b)
    {
        return (... + (getp(a) * getp(b)));
    }

But first-class parameter packs seem like a nightmare to specify, don't they?

Um, how exactly is that easier than the user implementing the appropriate interfaces? It certainly isn't easier for the reader to understand.

My idea was that std::getp would probably be standardized as the "packful" version of std::get, so the user wouldn't have to actually write out the implementation of getp in practice.
I agree that the syntax inside this version of dotproduct is much more off-putting than the postfix-twiddle syntax. It is uglier. However, it is conceptually cleaner, because it doesn't introduce any significantly new grammar (no postfix-twiddle operator) and it doesn't introduce any new "magic names" (no hard-coding of the names get and tuple_element_t into the compiler).  This is a trade-off, and I'm honestly not sure which way would be better for the language.  It's quite possible (IMHO) that the best course of action for now would be not doing either one.

–Arthur

Nicol Bolas

unread,
Jan 30, 2016, 11:33:22 AM1/30/16
to ISO C++ Standard - Future Proposals
On Friday, January 29, 2016 at 8:59:22 PM UTC-5, Arthur O'Dwyer wrote:
On Fri, Jan 29, 2016 at 1:08 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Friday, January 29, 2016 at 2:21:00 PM UTC-5, Arthur O'Dwyer wrote:

As of C++11, we have decltype(E) — which takes the value E and produces its type — and we have std::declval<T>() — which takes a type T and produces a value of that type. And they compose, so I can do a bunch of "value-space" computations and then take the decltype of the resulting expression. Any construct that gets in the way of my doing that is objectionable.
In other words, rather than saying that mpl::vector<Ts...> doesn't have a value and so std::get<I>(mplvec) should be ill-formed, I would prefer we simply say that std::get<I>(mplvec) doesn't have a value — but it should still have a type!

You can want whatever you want, but the fact is that in C++, types and values are different things. And we have different mechanisms for dealing with each.

Functions take values and return them. Template metafunctions take types and can evaluate to types. `declval` doesn't really generate a value in any meaningful way, since it can only exist in a non-evaluated context. And while `decltype` does evaluate to a type, that type is not based on a value, since the expression itself is never actually evaluated.

Maybe sometimes these things will be equivalent someday. But until then, C++ is what it is. Values and types are different, and we shouldn't pretend that they're not.

That's one point of view, but I tend to think that it's outdated (as of C++11). We don't need template metafunctions anymore (well, not so many of them),

No, there's a huge difference between "we don't need X anymore" and "we don't need X quite as much anymore".

Expression type deduction is useful, and I never claimed that it wasn't. But it should not be abused by declaring functions you don't intend to actually call, just so that you can turn what should be a metafunction invocation into `decltype(function)`.
 
Values and types are different, but they are still intimately related (for example, every value has a type), and while we can use different mechanisms to deal with each, we no longer need to.

And that way lies highly unclear code. If a template function has access to a type that provides a type sequence (just a sequence like mpl::vector), and you want to access one of the types in that type sequence, you have to do something like this:

decltype(get<I>(declval<TypeSeq>()))

Compare that to:

tuple_element_t<TypeSeq, I>

Which one of these is more clear to the reader what is going on?

So yes, if you want to blur the distinction between types and values in C++, you can. But I can't say much for the consequences of that.
 
Having said all that, this "postfix twiddle means std::get<> (and/or std::tuple_element_t<>)" thing still feels too ad-hoc to me.

It's no more ad-hoc than range-based for using `std::begin` and `std::end`. You have to have some customization point for user-defined types.

Agreed, I think, grudgingly, reluctantly, in practice.  In other words, I agree that's the current state of the world, but I don't like it. It feels much too ad-hoc. begin and end "work" as well as they do because they had a couple of decades of implementation experience behind them before people started making up syntactic sugar for them. Right now we have maybe 5 years of experience using std::get and probably even less with std::tuple_element_t. Are those exactly the primitives we want to syntactic-sugar up, or will we regret the decision in another 5 years when we realize "how tuples should have been done"?

I hate using principles as bludgeons, but "perfect is the enemy of good".

Sure, in 10 years, someone might come up with a better idea. But right now, today, this is a reasonable choice. Waiting around for 10 years for the best possible choice to present itself makes no sense. We have problems using tuples and tuple-like types, and we need solutions to those problems. Now.

Unless you have a specific, actual problem with the syntax, something that clearly makes it less effective or useful than it could be, then you're saying nothing more than "don't do this, it might not be good." Yes, we know it might not work out. That's not a good enough reason to stop trying.

Equally importantly, the language can change. Look at the Ranges TS, which changes iterator pairs into iterator+sentinel. C++14 range-based for loops assume that the type that `begin` and `end` return for the same range is the same. But all it takes is one small language change (the definition of how range-for transforms itself) to remove that requirement.

If we find a better way to handle tuples, a better entrypoint or whatever, we can always modify the meaning of the operator. You simply have to make the change in a non-code-breaking way.

I mean, imagine if instead of iostreams using operator overloading, we had gotten a syntactic sugar where << in certain contexts expanded to a call to std::printf(). Makes sense, right? But it would have been horrible for the flexibility and symmetry of the language as a whole.

Um... what's so bad about that?

Ignoring the silliness of changing what `<<` means, using `printf` as an extension point for formatting types makes far more sense than `operator<<`. Why? Because `operator<<` only gets two parameters, while a regular function can get however many parameters you want.

Regular printf allows you to do things like cap the width of a formatted integer or fill it out with zeros or whatever. These are encoded in the formatting string, but they are still effectively parameters to the formatter.

Iostreams provides equivalent functionality, but only by storing them in the formatter itself. Which means that they continue to apply until another command changes them; they're not bound to a specific value being formatted. This makes the construct exceedingly painful, on pretty much everyone:

1. It makes it harder to write a formatter. You have to extract your formatting parameters from the stream and then generate your data based on them.
2. Every stream must have the capacity to store such parameters, so streams have to do more stuff.
3. Users have to use Godawful syntax to turn such parameters on and off.

In short, `operator<<` does a crappy job of providing extensibility for formatting operations.

The only problem I have with this suggestion of having `<<` become `printf`is that you used a pre-existing operator that already has a well-defined meaning. If you had suggested using a new operator (which is what we're suggesting here), I'd be more-or-less OK with it.

Or at least less hostile (I personally despise using any operator for formatting).

The only thing I can think to do is to introduce first-class parameter packs, and write

    template<size_t I, class... Ts>
    auto getp(const std::tuple<Ts...>&) -> Ts...;  // "return a pack"

    template<class Vec>
    auto dotproduct(const Vec& a, const Vec& b)
    {
        return (... + (getp(a) * getp(b)));
    }

But first-class parameter packs seem like a nightmare to specify, don't they?

Um, how exactly is that easier than the user implementing the appropriate interfaces? It certainly isn't easier for the reader to understand.

My idea was that std::getp would probably be standardized as the "packful" version of std::get, so the user wouldn't have to actually write out the implementation of getp in practice.
I agree that the syntax inside this version of dotproduct is much more off-putting than the postfix-twiddle syntax. It is uglier. However, it is conceptually cleaner, because it doesn't introduce any significantly new grammar (no postfix-twiddle operator)

It does introduce new grammar; just not at the cite of use. You have to have new grammar to have multiple return values.

and it doesn't introduce any new "magic names" (no hard-coding of the names get and tuple_element_t into the compiler).  This is a trade-off, and I'm honestly not sure which way would be better for the language.  It's quite possible (IMHO) that the best course of action for now would be not doing either one.

No, it would not. Irrational fear is not a good reason to not do something. Especially if it is an exceedingly useful something, which will make a pain point of the language far less painful.

Equally importantly, it's not a tradeoff. The reason being that you cannot store arbitrary value sequences. They're not types, after all. You can't pass around a value sequence. These are all things that don't make sense because value sequences are not objects (they're multiple objects, each with their own type and so forth).

Tuples are objects. You can stick them in classes with well-defined behavior. You can pass them around intact, unbroadcasted. You can introspect them arbitrarily, fetching out a specific element. You can modify them in-situ. And so forth. Even with language-level multiple return values, tuples are still extremely useful constructs.

Which means that the desire to broadcast them, to transform tuples into value sequences, is still useful. So even if you have MRVs as a language-feature, you still want the tuple-to-value-sequence transformation.

So it isn't a tradeoff; the two features have some overlap, but they are also different and each are useful in their own rights.

Ville Voutilainen

unread,
Jan 30, 2016, 11:39:12 AM1/30/16
to ISO C++ Standard - Future Proposals
On 30 January 2016 at 18:33, Nicol Bolas <jmck...@gmail.com> wrote:
>> That's one point of view, but I tend to think that it's outdated (as of
>> C++11). We don't need template metafunctions anymore (well, not so many of
>> them),
> No, there's a huge difference between "we don't need X anymore" and "we
> don't need X quite as much anymore".
> Expression type deduction is useful, and I never claimed that it wasn't. But
> it should not be abused by declaring functions you don't intend to actually
> call, just so that you can turn what should be a metafunction invocation
> into `decltype(function)`.


Well, you may call it abuse, but most existing traits internally
declare functions
that are not intended to be called. Which means that declaring but not defining
functions is a fairly common metaprogramming technique.

Tony V E

unread,
Jan 30, 2016, 12:35:58 PM1/30/16
to Arthur O'Dwyer
IMO

mpl::vector is a "type-list"
std::tuple is a "tuple-like"‎ (and also a type-list) - a typelist with values

Can we use those terms going forward?


Any place you are doing meta-programming and need a type-list you _could_ use std::tuple, and the value part of tuple is ignored without penalty, as you are just doing compile-time type stuff.

HOWEVER, it is confusing to the reader. We need an actual std::typelist

At which point mpl::vector becomes "typelist-like" :-(
(ie we need to be careful about naming our types and our concepts the same thing or not)

And I hate std::get. I wish it was called tuple_get. Particularly if we start to use it as a syntax-sugar extension point (like range-for and begin/end)

‎Just my two cents. Carry on. 
Tony


Sent from my BlackBerry portable Babbage Device
From: Arthur O'Dwyer
Sent: Monday, January 25, 2016 2:37 PM
Subject: Re: [std-proposals] Re: RFC: Unpacking tuples to value sequences

--

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

Matthew Woehlke

unread,
Feb 3, 2016, 11:03:31 AM2/3/16
to std-pr...@isocpp.org
On 2016-01-30 11:33, Nicol Bolas wrote:
> On Friday, January 29, 2016 at 8:59:22 PM UTC-5, Arthur O'Dwyer wrote:
>> My idea was that std::getp would probably be standardized as the
>> "packful" version of std::get, so the user wouldn't have to actually
>> write out the implementation of getp in practice.
>> I agree that the syntax inside this version of dotproduct is much more
>> off-putting than the postfix-twiddle syntax. *It is uglier.* However, it
>> is *conceptually cleaner*, because it doesn't introduce any significantly
>> new grammar (no postfix-twiddle operator)
>
> It does introduce new grammar; just not at the cite of *use*. You have to
> have new grammar to have multiple return values.

Not only that, but you're no longer returning a tuple-like, you're
returning *an actual parameter pack*. This implies, by extension, that
you can do things like assign that pack to a variable:

auto p = getp(t);

...which in turn implies:

template <typename... Ts>
foo(Ts const&... args)
{
auto x = args;
bar(x...);
}

This of course has all sorts of interesting implications... what is
`decltype(x)`? Can I pass `x` as a "single" parameter to a function? Can
I pass multiple packs as distinct entities (i.e. and still know on the
other side what belongs to which pack)?

You can weasel out of this by claiming that getp only works in certain
contexts, but then... you've invented a grammar disguised as a function.
In any case, I don't see an improvement over something that's a grammar
and not trying to pretend otherwise.

Also, I still want slicing.

>> and it doesn't introduce any new "magic names" (no hard-coding of the names
>> get and tuple_element_t into the compiler).

...which is completely irrelevant. In light of P0144, we *already have
that*. Inventing a new mechanism to do *the exact same thing*¹ is idiocy.

(¹ Both P0144 and this are value unpacking. This unpacks into a
parameter pack, P0144 into a variable assignment, but before those
happen, both involve the same conceptual decomposition of a tuple-like
into a sequence of values.)

--
Matthew

Arthur O'Dwyer

unread,
Feb 3, 2016, 7:28:20 PM2/3/16
to std-pr...@isocpp.org
On Wed, Feb 3, 2016 at 8:03 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
On 2016-01-30 11:33, Nicol Bolas wrote:
> On Friday, January 29, 2016 at 8:59:22 PM UTC-5, Arthur O'Dwyer wrote:
>> My idea was that std::getp would probably be standardized as the
>> "packful" version of std::get, so the user wouldn't have to actually
>> write out the implementation of getp in practice.
>> I agree that the syntax inside this version of dotproduct is much more
>> off-putting than the postfix-twiddle syntax. *It is uglier.* However, it
>> is *conceptually cleaner*, because it doesn't introduce any significantly
>> new grammar (no postfix-twiddle operator)
>
> It does introduce new grammar; just not at the cite of *use*. You have to
> have new grammar to have multiple return values.

Not only that, but you're no longer returning a tuple-like, you're
returning *an actual parameter pack*. This implies, by extension, [...]

all sorts of interesting implications...

Right. Adding "first-class parameter packs" would definitely open many cans of worms, which is why the Committee hasn't done it yet. It adds many interesting language issues (as opposed to library issues). However, I stand by my assertion that it doesn't add any new grammar issues.
 
what is `decltype(x)`?

Well, since x is a pattern, decltype(x) would also be a pattern. You could turn it into a pack expansion by adding ... on the end.
 
Can I pass `x` as a "single" parameter to a function? Can
I pass multiple packs as distinct entities (i.e. and still know on the
other side what belongs to which pack)?

There's no existing grammar to allow that, so I guess not.

>> and it doesn't introduce any new "magic names" (no hard-coding of the names
>> get and tuple_element_t into the compiler).

...which is completely irrelevant. In light of P0144, we *already have
that*. Inventing a new mechanism to do *the exact same thing*¹ is idiocy.

You're right. If P0144 is adopted, it will show that the Committee is friendly to the idea of hard-coding the name "std::get" into the compiler. If that happens, then my objection to "magic names" will clearly have been overruled, and I'll happily shut up about it. The reason I'm harping on it right now is that I'm not aware that the Committee has shown significant friendliness toward P0144 yet.
In other words: Ranged for-loop syntax is a minor, but IMHO inconclusive, precedent in favor of hard-coding magic names. Adoption of P0144 would be a major precedent in favor — I'm just not aware that P0144 has in fact been adopted. :)

–Arthur

Reply all
Reply to author
Forward
0 new messages