[tuple] extracting tuples out of a tuple

918 views
Skip to first unread message

Oliver Kowalke

unread,
Feb 12, 2016, 2:34:58 AM2/12/16
to std-pr...@isocpp.org
In one project I've had the need for extracting tuples from the head/tail of a given tuple.
Looking at cppreference.com it seams such functions are not part of the standard yet.
Does a proposal exist that deals with this topic?

I would suggest something like:

std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
std::tuple< int, std::string, std::string > tpl1;
std::tuple< std::string, double > tpl2;

head( tpl1) = tpl;
tail( tpl2) = tpl;
split( tpl1, tpl2) = tpl;

Oliver

Matthew Woehlke

unread,
Feb 12, 2016, 10:06:37 AM2/12/16
to std-pr...@isocpp.org
Here's what *I'd* like:

auto tpl1 = std::make_tuple([:3]tpl...);
auto tpl2 = std::make_tuple([3:]tpl...);

For bonus points, this works on any tuple-*like*, not just std::tuple (I
guess that would be possible with a library solution, but it would make
the implementation rather more complicated, whereas the above requires
no new library features at all).

(Given your prior declarations of `tpl1` and `tpl2`, it may be possible
to do the assignment like `tpl1 = {[:3]tpl...}`.)

I'm not a fan of using magical references to do indirect assignment,
i.e. any of your proposals, but especially `split`. All of these seem
backwards, i.e. should be `tpl1 = head<3>(tpl);` instead.

--
Matthew

Oliver Kowalke

unread,
Feb 12, 2016, 11:31:26 AM2/12/16
to std-pr...@isocpp.org
2016-02-12 16:06 GMT+01:00 Matthew Woehlke <mwoehlk...@gmail.com>:

I'm not a fan of using magical references to do indirect assignment,
i.e. any of your proposals, but especially `split`. All of these seem
backwards, i.e. should be `tpl1 = head<3>(tpl);` instead.

I've had std::tie() in mind,

int i...;
X x...;
std::tie( i, x) = get_tuple();

Matthew Woehlke

unread,
Feb 12, 2016, 12:11:35 PM2/12/16
to std-pr...@isocpp.org
That's also a horribly ugly interface that we shouldn't be perpetuating;
http://wg21.link/p0144 solves that exact example ever so much better...

At any rate, 'tie' is a much better name for a magic assignment
helper... mainly, because the verb matches the actual action that is
occurring (I could live with 'splice' more than 'split').

--
Matthew

Arthur O'Dwyer

unread,
Feb 12, 2016, 7:28:12 PM2/12/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Friday, February 12, 2016 at 9:11:35 AM UTC-8, Matthew Woehlke wrote:
On 2016-02-12 11:30, Oliver Kowalke wrote:
> 2016-02-12 16:06 GMT+01:00 Matthew Woehlke <mwoehlk...@gmail.com>:
>> I'm not a fan of using magical references to do indirect assignment,
>> i.e. any of your proposals, but especially `split`. All of these seem
>> backwards, i.e. should be `tpl1 = head<3>(tpl);` instead.
>
> I've had std::tie() in mind,
>
> int i...;
> X x...;
> std::tie( i, x) = get_tuple();

That's also a horribly ugly interface that we shouldn't be perpetuating;
http://wg21.link/p0144 solves that exact example ever so much better...

I think maybe Oliver was trying to say that

    tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
    tuple
<string, string, double> tpl1;
    tpl1
= tail(tpl);    // Python: tpl1 = tpl[1:]
    tail
(tpl) = tpl1;    // Python: tpl[1:] = tpl1


should both work, because tail() should return a tuple of references instead of a tuple of values. I definitely support not-making-unnecessary-copies in general, and particularly in this case if it lets tail() work like tie(). But it does seem a little bit non-orthogonal.  (Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)

Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))). But that's just poor naming. The underlying operation here is either "splice" (extract a contiguous range of indices from the input tuple, as in the Python above) or "select" (extract a noncontiguous set of indices from the input tuple and compress them, as in various vector instruction sets).

I agree that

    std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
    std
::tuple< int, std::string, std::string > tpl1;
    std
::tuple< std::string, double > tpl2;

    split
( tpl1, tpl2) = tpl;

is just nonsense, as written.
However, under the "unpacking with postfix tilde" not-a-proposal, a similar operation could be written like this:

    tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
    tuple
<int, string> tpl1;
    tuple
<string, double> tpl2;

    std
::tie(tpl1~..., tpl2~...) = tpl;

   
assert(std::get<0>(tpl1) == std::get<0>(tpl));
   
assert(std::get<1>(tpl1) == std::get<1>(tpl));
   
assert(std::get<0>(tpl2) == std::get<2>(tpl));
   
assert(std::get<1>(tpl2) == std::get<3>(tpl));

In general, I think that the C++ standard library is woefully lacking in tuple manipulators... but rather than proposing new tuple manipulators (of which there could be a million: zip, fold, map, car, cdr, reverse,...), we should be trying to give people the ability to express those manipulators natively in the language, via something like Matthew's [:] notation or my ~... notation.

my $.02,
–Arthur

Oliver Kowalke

unread,
Feb 13, 2016, 3:45:24 AM2/13/16
to std-pr...@isocpp.org
016-02-13 1:28 GMT+01:00 Arthur O'Dwyer <arthur....@gmail.com>:
(Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)

correct
 
Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))).

head(x) should return the part from the source tuple's head that fits into tuple x, same applies to tail(x)
 
But that's just poor naming. The underlying operation here is either "splice"

I'm only interested in the functionality, not in the naming. One of my project requires such functionality and I was wondering that neither the standard
provides such manipulators nor could I find a proposal.

tuple ~... and tuple [:] require a language modification - but I need a solution now; so I've written head()/tail() (even if the naming is not perfect).

Vicente J. Botet Escriba

unread,
Feb 13, 2016, 4:06:50 AM2/13/16
to std-pr...@isocpp.org, mwoehlk...@gmail.com
Le 13/02/2016 01:28, Arthur O'Dwyer a écrit :
On Friday, February 12, 2016 at 9:11:35 AM UTC-8, Matthew Woehlke wrote:
On 2016-02-12 11:30, Oliver Kowalke wrote:
> 2016-02-12 16:06 GMT+01:00 Matthew Woehlke <mwoehlk...@gmail.com>:
>> I'm not a fan of using magical references to do indirect assignment,
>> i.e. any of your proposals, but especially `split`. All of these seem
>> backwards, i.e. should be `tpl1 = head<3>(tpl);` instead.
>
> I've had std::tie() in mind,
>
> int i...;
> X x...;
> std::tie( i, x) = get_tuple();

That's also a horribly ugly interface that we shouldn't be perpetuating;
http://wg21.link/p0144 solves that exact example ever so much better...

I think maybe Oliver was trying to say that

    tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
    tuple
<string, string, double> tpl1;
    tpl1
= tail(tpl);    // Python: tpl1 = tpl[1:]
    tail
(tpl) = tpl1;    // Python: tpl[1:] = tpl1


should both work, because tail() should return a tuple of references instead of a tuple of values. I definitely support not-making-unnecessary-copies in general, and particularly in this case if it lets tail() work like tie(). But it does seem a little bit non-orthogonal.  (Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)

I don't think Oliver made some typos, but it is up to him to confirm. Anyway the name was not good.


std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
std::tuple< int, std::string, std::string > tpl1;
std::tuple< std::string, double > tpl2;

head( tpl1) = tpl;
tail( tpl2) = tpl;
split( tpl1, tpl2) = tpl;

head(tpl1) returns a reference to tuple that accepts assignment form tuples having the same types from the front and
tail(tpl2) do the opposite, returns a reference to tuple that accepts assignments form tuples having the same types from the back.

head->tie_tuple_at_front
tail->tie_tuple_at_back

It could also be a tie_tuple_at_middle that accepts assignment form tuples having the same types from a unspecified position.

My names are not good neither.

I believe that his split pretended to be a equivalent to his


head( tpl1) = tpl;
tail( tpl2) = tpl;

but I don't see the added value.

Whether it is worth having this in the standard and looking for a good names is another question.




Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))). But that's just poor naming. The underlying operation here is either "splice" (extract a contiguous range of indices from the input tuple, as in the Python above) or "select" (extract a noncontiguous set of indices from the input tuple and compress them, as in various vector instruction sets).
I'm not saying that a splice function on tuples is not useful, but IMHO Oliver idea was to determine the splice to do depending on the target tuple not on the source tuple.



I agree that

    std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
    std
::tuple< int, std::string, std::string > tpl1;
    std
::tuple< std::string, double > tpl2;
    split
( tpl1, tpl2) = tpl;

is just nonsense, as written.
See above.

However, under the "unpacking with postfix tilde" not-a-proposal, a similar operation could be written like this:

    tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
    tuple
<int, string> tpl1;
    tuple
<string, double> tpl2;

    std
::tie(tpl1~..., tpl2~...) = tpl;

   
assert(std::get<0>(tpl1) == std::get<0>(tpl));
   
assert(std::get<1>(tpl1) == std::get<1>(tpl));
   
assert(std::get<0>(tpl2) == std::get<2>(tpl));
   
assert(std::get<1>(tpl2) == std::get<3>(tpl));

In general, I think that the C++ standard library is woefully lacking in tuple manipulators... but rather than proposing new tuple manipulators (of which there could be a million: zip, fold, map, car, cdr, reverse,...), we should be trying to give people the ability to express those manipulators natively in the language, via something like Matthew's [:] notation or my ~... notation.


Just wondering how could help the no-yet-proposed operators  [:], ~, and  ... to implement what I interpreted.

Vicente

Vicente J. Botet Escriba

unread,
Feb 13, 2016, 4:18:25 AM2/13/16
to std-pr...@isocpp.org
Le 13/02/2016 09:44, Oliver Kowalke a écrit :
016-02-13 1:28 GMT+01:00 Arthur O'Dwyer <arthur....@gmail.com>:
(Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)

correct
I'm confused. Arthur's was not doing an extraction but an insertion in this case


    tail(tpl) = tpl1;    // Python: tpl[1:] = tpl1

I believed you were talking of extraction.

 
Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))).

head(x) should return the part from the source tuple's head that fits into tuple x, same applies to tail(x)
 
But that's just poor naming. The underlying operation here is either "splice"

I'm only interested in the functionality, not in the naming. One of my project requires such functionality and I was wondering that neither the standard
provides such manipulators nor could I find a proposal.

tuple ~... and tuple [:] require a language modification - but I need a solution now; so I've written head()/tail() (even if the naming is not perfect).

I agree that we should first think in terms of the desired functionality and if it is worth having it in the standard. If some additional language features make this easier great.
I believe that you would need to describe more precisely what you want these functions to do.

Vicente

Oliver Kowalke

unread,
Feb 13, 2016, 4:43:40 AM2/13/16
to std-pr...@isocpp.org
2016-02-13 10:18 GMT+01:00 Vicente J. Botet Escriba <vicent...@wanadoo.fr>:
Le 13/02/2016 09:44, Oliver Kowalke a écrit :
016-02-13 1:28 GMT+01:00 Arthur O'Dwyer <arthur....@gmail.com>:
(Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)

correct
I'm confused. Arthur's was not doing an extraction but an insertion in this case

sorry - I was confused
 

    tail(tpl) = tpl1;    // Python: tpl[1:] = tpl1

I believed you were talking of extraction.

extraction - I'm not familiar with python
 
 
Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))).

head(x) should return the part from the source tuple's head that fits into tuple x, same applies to tail(x)
 
But that's just poor naming. The underlying operation here is either "splice"

I'm only interested in the functionality, not in the naming. One of my project requires such functionality and I was wondering that neither the standard
provides such manipulators nor could I find a proposal.

tuple ~... and tuple [:] require a language modification - but I need a solution now; so I've written head()/tail() (even if the naming is not perfect).

I agree that we should first think in terms of the desired functionality and if it is worth having it in the standard. If some additional language features make this easier great.
I believe that you would need to describe more precisely what you want these functions to do.

the code required something like:
...
auto args = std::move( std::get< 1 >( * tpl) );
auto result = apply( args);
detail::tail( args) = result;
...

output of this code:


        std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
        std::tuple< int, std::string, std::string > tpl1;
        std::tuple< std::string, double > tpl2;
        head( tpl1) = tpl;
        tail( tpl2) = tpl;
        std::cout << " tpl: " << tpl << "\n";
        std::cout << " tpl1: " << tpl1 << "\n";
        std::cout << " tpl2: " << tpl2 << "\n";

should be:

       tpl: (-1, abc, xyz, 0.5)
       tpl1: (-1, abc, xyz)
       tpl2: (xyz, 0.5)

Oliver Kowalke

unread,
Feb 13, 2016, 4:47:02 AM2/13/16
to std-pr...@isocpp.org
2016-02-13 10:43 GMT+01:00 Oliver Kowalke <oliver....@gmail.com>:
auto result = apply( args);

sorry - I removed too much from the code, I mean 'auto result = apply( fn, args);' were fn is a callable and result is a tuple returned by the callable
 

David Krauss

unread,
Feb 13, 2016, 5:03:36 AM2/13/16
to std-pr...@isocpp.org

On 2016–02–13, at 12:30 AM, Oliver Kowalke <oliver....@gmail.com> wrote:

I've had std::tie() in mind,

The special feature of tie is generating references. That seems orthogonal to slicing a sequence. Reference semantics should be used sparingly in general.

How about a function to get a pack of references from a tuple,

template< typename ... t >
std::tuple< t & ... > tuple_ref( std::tuple< t ... > & );

and a generic slice function,

template< std::size_t first, std::size_t last, typename tuple_like >
std::tuple< std::tuple_element_t< tuple_like, N > ... > // N goes from first to last.
tuple_slice( tuple_like const & );

Then the example looks like,

std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
std::tuple< int, std::string, std::string > tpl1;
std::tuple< std::string, double > tpl2;

tpl1 = tuple_slice< 0, 3 >( tpl );
tpl2 = tuple_slice< 2, 2 >( tpl );
std::tuple_cat( tuple_ref( tpl1 ), tuple_ref( tpl2 ) ) = tpl;

The bounds of the slice are explicit, but that looks more like a feature than a bug.

Matthew Woehlke

unread,
Feb 15, 2016, 10:21:30 AM2/15/16
to std-pr...@isocpp.org
On 2016-02-13 05:03, David Krauss wrote:
>> On 2016–02–13, at 12:30 AM, Oliver Kowalke wrote:
>>
>> I've had std::tie() in mind,
>
> How about a function to get a pack of references from a tuple,

Please, no; we already *have* std::tie, we don't need others (including
split/splice):

// David's tuple_ref
std::tie([:]t...);

// same as proposed std::split
std::tie([:]tpl1..., [:]tpl2...) = std::make_tuple([:]tpl...);

The RHS (second example) can of course be replaced with just `tpl` if
`tpl` is already a std::tuple. Otherwise, this works for any
*tuple-likes*, not just actual std::tuple's, provided that the LHS
get<N>(T&) returns a reference (as it does for e.g. std::tuple, and
presumably would for aggregates when using the implicitly generated
version a la P0197).

> and a generic slice function,
> [snip declaration]
>
> tpl1 = tuple_slice< 0, 3 >( tpl );
> tpl2 = tuple_slice< 2, 2 >( tpl );
> std::tuple_cat( tuple_ref( tpl1 ), tuple_ref( tpl2 ) ) = tpl;

I like that you're thinking along the lines of not creating a magical
assignment operator (which is what the proposed head/tail would be; they
necessarily would create a "magical" helper class with a templated
assignment operator which consumes an *arbitrary* tuple of at least N
elements and assigns the first N elements to the N elements of the LHS).

I like less that you're still looking at a library solution that only
handles std::tuple. Language unpacking/slicing handles *any tuple-like*
(on the RHS, and any whose get<N> returns a reference on the LHS).

--
Matthew

Matthew Woehlke

unread,
Feb 15, 2016, 10:55:08 AM2/15/16
to std-pr...@isocpp.org
On 2016-02-12 19:28, Arthur O'Dwyer wrote:
> I think maybe Oliver was trying to say that
>
> tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
> tuple<string, string, double> tpl1;
> tpl1 = tail(tpl); // Python: tpl1 = tpl[1:]
> tail(tpl) = tpl1; // Python: tpl[1:] = tpl1
>
> should both work, because tail() should return a tuple of references
> instead of a tuple of values.

No, that's definitely *not* how I understood it. See Vicente's reply.

Actually, thinking about it, `splice` *might* be useful enough to keep
as a function in its own right:

template <typename... Tuples>
splice(Tuples&... tuples)
{
return std::tie(([:]tuples...)...);
}

(Bonus points: would the above syntax actually work? What syntax *would*
work? The goal is: `get<0>(a), ... get<A>(a), get<0>(b), ... get<B>(b),
...`.)

The others (head, tail) don't work this way at all (again, see Vicente's
explanation). Using one of these on the RHS of an expression would be an
error. Neither of your examples above are correct. Rather:

tail(tpl1) = tpl; // Python: tpl1 = tpl[len(tpl) - len(tpl1):]

> In general, I think that the C++ standard library is woefully lacking in
> tuple manipulators... but rather than proposing new tuple manipulators (of
> which there could be a million: zip, fold, map, car, cdr, reverse,...), we
> should be trying to give people the ability to express those manipulators
> natively in the language, via something like Matthew's [:] notation or my
> ~... notation.

...and this thread is a great example of the superiority of `[:]` ;-).
(Because it's not *just* `[:]`, it's `[A:B]` where both of `A` and `B`
are optional. That's harder to do with `~`... I suppose you could write
something like `tpl~{0:2}`, but that's getting a little ugly.)

--
Matthew

Matthew Woehlke

unread,
Feb 15, 2016, 10:55:09 AM2/15/16
to std-pr...@isocpp.org
On 2016-02-13 03:44, Oliver Kowalke wrote:
> 016-02-13 1:28 GMT+01:00 Arthur O'Dwyer:
>> Also, of course, head(x) should logically return get<0>(x), not some weird
>> synonym for reverse(tail(reverse(x))).
>
> head(x) should return the part from the source tuple's head that fits into
> tuple x, same applies to tail(x)

Where source == tpl and x == tpl1, correct?

The problem is, the implementation for that has to look something like:

template <typename... types>
head_helper head(tuple<types...>& dst) { return {dst}; }

template <typename... types>
class head_helper
{
head_heler(tuple<types...> dst) : dst{dst} {}

template <typename... extra>
tuple<types...>& operator=(tuple<types..., extra...> const& src);
// implementation details elided

tuple<types...>& dst;
};

...and similar for `tail`.

> tuple ~... and tuple [:] require a language modification - but I need a
> solution now; so I've written head()/tail() (even if the naming is not
> perfect).

Then proposing a standard library modification is not going to help you
either; in both cases, you'll need to implement a work-around until at
least C++20. General splicing (i.e. `[:]`) can do the same tasks as your
proposed library features, but it does them *better* (not limited to
std::tuple), and it does *a whole lot more*.

--
Matthew

Oliver Kowalke

unread,
Feb 15, 2016, 2:12:46 PM2/15/16
to std-pr...@isocpp.org
2016-02-15 16:54 GMT+01:00 Matthew Woehlke <mwoehlk...@gmail.com>:

Then proposing a standard library modification is not going to help you
either; in both cases, you'll need to implement a work-around until at
least C++20.

actually I was asking for an existing proposal - but I couldn't found one
 
General splicing (i.e. `[:]`) can do the same tasks as your
proposed library features, but it does them *better* (not limited to
std::tuple), and it does *a whole lot more*.

the '[:] ' - syntax looks ugly for me - I would prefer a library solution (which could support
std::array too).

Arthur O'Dwyer

unread,
Feb 15, 2016, 3:55:01 PM2/15/16
to std-pr...@isocpp.org
On Mon, Feb 15, 2016 at 7:54 AM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> On 2016-02-12 19:28, Arthur O'Dwyer wrote:
>> I think maybe Oliver was trying to say that
>>
>>     tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
>>     tuple<string, string, double> tpl1;
>>     tpl1 = tail(tpl);    // Python: tpl1 = tpl[1:]
>>     tail(tpl) = tpl1;    // Python: tpl[1:] = tpl1
>>
>> should both work, because tail() should return a tuple of references
>> instead of a tuple of values.
>
> No, that's definitely *not* how I understood it. See Vicente's reply.

Yep, I was definitely wrong in my interpretation of what Oliver meant. My standard for "no way, he couldn't possibly mean..." was set too high. ;)


> Actually, thinking about it, `splice` *might* be useful enough to keep
> as a function in its own right:
>
>   template <typename... Tuples>
>   splice(Tuples&... tuples)
>   {
>     return std::tie(([:]tuples...)...);
>   }

Isn't this (concatenation of a list of tuples) just tuple_cat?
http://en.cppreference.com/w/cpp/utility/tuple/tuple_cat

The missing "primitive" IMHO would be "take a tuple-like and apply std::ref() to each argument, creating a tuple of references"; which could be spelled either
    std::tie(x~...)
or
    std::make_tuple(std::ref(x~)...)

> (Bonus points: would the above syntax actually work? What syntax *would*
> work? The goal is: `get<0>(a), ... get<A>(a), get<0>(b), ... get<B>(b),
> ...`.)

Yes, I think the syntax above would work (modulo my continued objection that [:], being a prefix operator, logically should not bind tighter than the postfix operator ...; and modulo the trivial missing auto).  However, oh god, let's not encourage such brain-twisting code.

Assuming I'm understanding correctly, the weirdness here is that packness "nests" or "stacks", and therefore pattern expressions in the new regime have to be evaluated "on a stack", kind of like type definitions.

    std::tie((tuples~...)...)

tuples is the name of a pack, and therefore it is also a pattern of "packness" 1.
The ~ operator "adds packness" to the expression. Therefore,
tuples~ is a pattern of packness 2.
tuples~... DOES NOT expand the pattern tuples~ into tuple0~, tuple1~, tuple2~, because patterns of packness >1 are never expanded right away. Instead, ... "subtracts packness" from the expression, so that tuples~... is itself a pattern of packness 1. Expanding it would yield tuple0~..., tuple1~..., tuple2~....
Finally, (tuples~...)... applies ... to a pattern of packness 1, yielding tuple0~..., tuple1~..., tuple2~.... Each of these expressions consists of ... applied to a pattern of packness 1, so they're all expanded, to yield the result we want.

I think this concept of "packness >1" would be completely new to C++. There's currently a place in the grammar where ...... is legal, but it's not inside expressions; it's a dumb interaction between packs and C-style variadic functions.

I've been trying to figure out whether "column-major" catting (basically tuple_cat(tuple_zip(tuple0, tuple1, tuple2)~...)) can be expressed compactly in tilde-notation / [:]-notation. I'm pretty sure it can't, but it's really hard to wrap my head around. Note that you may assume tuple0, tuple1, and tuple2 all have the same size.


>> In general, I think that the C++ standard library is woefully lacking in
>> tuple manipulators... but rather than proposing new tuple manipulators (of
>> which there could be a million: zip, fold, map, car, cdr, reverse,...), we
>> should be trying to give people the ability to express those manipulators
>> natively in the language, via something like Matthew's [:] notation or my
>> ~... notation.
>
> ...and this thread is a great example of the superiority of `[:]` ;-).
> (Because it's not *just* `[:]`, it's `[A:B]` where both of `A` and `B`
> are optional. That's harder to do with `~`... I suppose you could write
> something like `tpl~{0:2}`, but that's getting a little ugly.)

Personally I'd just do it with

    std::tuple_slice<0,2>(tpl)~

:)  Keep in mind that I don't see "slicing" as a primitive operation; I think slicing can be done efficiently with a library solution (which is to say, it doesn't add any new expressiveness to the language). Tuple-unpacking-into-packs, OTOH, definitely adds expressiveness.

(I'm assuming that in the new regime, all the tuple helpers, such as tuple_cat and tuple_slice and tuple_zip and whatnot, would perfectly forward all their arguments through to the result, just like std::forward_as_tuple. This is not currently the case for C++14's std::tuple_cat. I don't understand why not. The proposal for std::tuple_cat was
which doesn't mention perfect forwarding as a concern; possibly the proposal predated the spread of perfect forwarding as a common pattern.)

–Arthur

Matthew Woehlke

unread,
Feb 15, 2016, 4:40:53 PM2/15/16
to std-pr...@isocpp.org
On 2016-02-15 15:54, Arthur O'Dwyer wrote:
> On Mon, Feb 15, 2016 at 7:54 AM, Matthew Woehlke wrote:
>> On 2016-02-12 19:28, Arthur O'Dwyer wrote:
>>> I think maybe Oliver was trying to say that
>>>
>>> tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
>>> tuple<string, string, double> tpl1;
>>> tpl1 = tail(tpl); // Python: tpl1 = tpl[1:]
>>> tail(tpl) = tpl1; // Python: tpl[1:] = tpl1
>>>
>>> should both work, because tail() should return a tuple of references
>>> instead of a tuple of values.
>>
>> No, that's definitely *not* how I understood it. See Vicente's reply.
>
> Yep, I was definitely wrong in my interpretation of what Oliver meant. My
> standard for "no way, he couldn't possibly mean..." was set too high. ;)

:-D

>> Actually, thinking about it, `splice` *might* be useful enough to keep
>> as a function in its own right:
>>
>> template <typename... Tuples>
>> splice(Tuples&... tuples)
>> {
>> return std::tie(([:]tuples...)...);
>> }
>
> Isn't this (concatenation of a list of tuples) just tuple_cat?
> http://en.cppreference.com/w/cpp/utility/tuple/tuple_cat

If that is extended to take *tuple-likes*, then sure :-). (It isn't
clear from the link, but I assume currently it only takes `std::tuple`s.
I don't see why extending it would be a problem.)

>> (Bonus points: would the above syntax actually work? What syntax *would*
>> work? The goal is: `get<0>(a), ... get<A>(a), get<0>(b), ... get<B>(b),
>> ...`.)
>
> Yes, I think the syntax above would work (modulo [...] the trivial
> missing auto).

Oops :-).

> However, oh god, let's not encourage such brain-twisting code.

Well, it would be in the standard library, at least :-) (i.e. as opposed
to user code).

> Assuming I'm understanding correctly, the weirdness here is that
> packness "nests" or "stacks", and therefore pattern expressions in the new
> regime have to be evaluated "on a stack", kind of like type definitions.
>
> [snip detailed explanation]

Yes, exactly.

>> ...and this thread is a great example of the superiority of `[:]` ;-).
>> (Because it's not *just* `[:]`, it's `[A:B]` where both of `A` and `B`
>> are optional. That's harder to do with `~`... I suppose you could write
>> something like `tpl~{0:2}`, but that's getting a little ugly.)
>
> Personally I'd just do it with
>
> std::tuple_slice<0,2>(tpl)~
>
> :) Keep in mind that I don't see "slicing" as a primitive operation; I
> think slicing can be done efficiently with a library solution (which is to
> say, it doesn't add any new expressiveness to the language).

What happens when you want to do this?

auto {x, y} = some_3d_point; // don't care about z

With tuple_slice:

auto {x, y} = tuple_slice<0,2>(make_tuple(some_3d_point~...));

With my `[:]`:

auto {x, y} = {[:2]some_3d_point...};

(Possibly the `{}`s and `...` could be optional in the above.)

That involves a temporary. (Now, obviously there are totally different
ways to accomplish the same thing that may be better, but I feel like
`[:2]` is the most terse. Also, it means we don't need to bother with
assignment-unpacking syntax for that case.)


Oh! Another reason why we might want slicing:

template <typename Arg> auto sum(Arg arg) { return arg; }
template <typename... Args> auto sum(Args... args)
{
return sum([0]{args...}) + sum([1:]{args...}...);
}

(Ignore that a fold expression would do this better. The point is that
this allows writing recursive variadic-template functions without the
ugly 'Head head, Tail... tail' style parameter lists.)

For bonus points, if we allow `[:]` to directly slice parameter packs,
we can simplify:

return sum([0]args) + sum(([1:]args)...);

// Notes:
[0]args; // single value, not a parameter pack
sizeof...([0]args); // illegal; not a parameter pack

[:]args; // same as 'args'
[:1]args; // still a parameter pack
[0:1]args; // also still a parameter pack
([0:1]args...); // same as '[0]args' (or error if sizeof...(args)==0)
sizeof...([:1]args); // == max(0, sizeof...(args) - 1)

--
Matthew

Louis Dionne

unread,
Feb 15, 2016, 6:22:24 PM2/15/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
Hi,

I'd like to quickly chime in to drop a link to Boost.Hana [1] (which I'm the 
author of, for full disclosure). There seems to be quite a bit of discussion 
about adding language features to manipulate packs and tuples, when a lot of 
this could be done in a library. Hana's purpose is specifically to manipulate 
tuples (and more generally heterogeneous containers) by providing std-like 
algorithms to operate on them. 

Reading the comments here, I just don't see the need for a new language 
feature for manipulating parameter packs. Instead, I think we need proper 
standard library features to manipulate tuples with a high level of 
abstraction. If properly designed, that could be much more flexible than 
a language feature in the long term, when we realize that we're missing 
something else. Just to give you a glimpse: how would you reverse a parameter 
pack? How would you sort a parameter pack based on a compile-time predicate? 
I don't see how these slicing proposals are of any help, yet this is a very 
real use case for metaprogramming. 

Instead, I think we need to carefully design a STL for metaprogramming (with 
customization points where it makes sense), and then let users build on top 
of that. And if you're worried about compile-times being too long with a 
library-based approach, this can be tackled with a few well-chosen compiler 
intrinsics (see this article [2]).

Regards,
Louis Dionne

Nicol Bolas

unread,
Feb 15, 2016, 9:10:52 PM2/15/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Monday, February 15, 2016 at 6:22:24 PM UTC-5, Louis Dionne wrote:
Hi,

I'd like to quickly chime in to drop a link to Boost.Hana [1] (which I'm the 
author of, for full disclosure). There seems to be quite a bit of discussion 
about adding language features to manipulate packs and tuples, when a lot of 
this could be done in a library. Hana's purpose is specifically to manipulate 
tuples (and more generally heterogeneous containers) by providing std-like 
algorithms to operate on them. 

Reading the comments here, I just don't see the need for a new language 
feature for manipulating parameter packs. Instead, I think we need proper 
standard library features to manipulate tuples with a high level of 
abstraction.

That's like saying, "Why do we need lambdas in the language? We have Boost.Lambda!"

Indeed, I seem to recall Boost.Lambda being one of the impetuses for getting language-based lambdas. BLL was basically proof that you couldn't do lambdas as a library. It said, "Look, this is the best the language can do as is: here's what you get, here's what you have to do to implement it, here's how ugly user-code looks, and here are all of the places where it breaks down".

In that regard, libraries like Fusion and Hana are perfect examples of why tuple unpacking needs to be a language feature.

Show me the Hana code for this:

outer(inner([:]tpl)...)

This calls `inner` on each element of the tuple, then pipes the result into the call to `outer`. It works exactly like parameter packs too, so if `tpl` were a pack, you just drop the `[:]` syntax and it works.

Show me the Hana code for this:

auto x = inner([:]tpl) + ...;

This simply calls a function on each element of the tuple and takes the sum of the results. Again, it works like parameter packs, so it reuses existing knowledge.

Oh, and show me the Hana code for this:

struct Data
{
 
int i;
 
float f;
 
double d;
};

Data d = ...;
outer
(inner([:]d)...);

It's the same as the first example, only using an aggregate.

It should also be noted that having compiler support for unpacking tuples does not mean you can't also have additional library functions for help in other cases. Sorting, reversing, etc could be library stuff, while the most common cases are handled by the direct language feature.
 
If properly designed, that could be much more flexible than 
a language feature in the long term, when we realize that we're missing 
something else. Just to give you a glimpse: how would you reverse a parameter 
pack? How would you sort a parameter pack based on a compile-time predicate? 
I don't see how these slicing proposals are of any help, yet this is a very 
real use case for metaprogramming. 

Instead, I think we need to carefully design a STL for metaprogramming (with 
customization points where it makes sense), and then let users build on top 
of that. And if you're worried about compile-times being too long with a 
library-based approach, this can be tackled with a few well-chosen compiler 
intrinsics (see this article [2]).

So instead of having language support for unpacking tuples, you want language support for... some low-level stuff that can be used to build a library?

No thanks; I'll take the simple and easy-to-use feature over the huge and complex STL-like thing.

Matthew Woehlke

unread,
Feb 16, 2016, 11:07:30 AM2/16/16
to std-pr...@isocpp.org
On 2016-02-15 21:10, Nicol Bolas wrote:
> On Monday, February 15, 2016 at 6:22:24 PM UTC-5, Louis Dionne wrote:
>> Reading the comments here, I just don't see the need for a new language
>> feature for manipulating parameter packs. Instead, I think we need proper
>> standard library features to manipulate tuples with a high level of
>> abstraction.
>
> That's like saying, "Why do we need lambdas in the language? We have
> Boost.Lambda!"
> [...]
>
> Show me the Hana code for [many examples]:

Wow... thanks, Nicol! Exactly what I would have said, only better :-).

>> If properly designed, that could be much more flexible than
>> a language feature in the long term, when we realize that we're missing
>> something else. Just to give you a glimpse: how would you reverse a
>> parameter pack? How would you sort a parameter pack based on a compile-time
>> predicate?

*Simple* unpacking solves a basic problem that is hard to solve as a
library solution (see previous discussion at
https://groups.google.com/a/isocpp.org/d/msg/std-proposals/PghsmqN1cAw/0Q1V-22lFAAJ):

foo([:]tl1..., [:]tl2...);

If we have simple unpacking (at least with `[:]`), it's trivial to add
slicing. Why not? It solves real problems and is much cleaner than a
library solution.

No, it doesn't solve reversing or sorting, but I rather doubt those are
nearly as common. Keep in mind all this is modeled heavily after Python,
so there is precedent for what operations are made trivial and which are
left relegated to more complex library solutions.

That all being said, I'm not seeing why these aren't orthogonal. We want
unpacking (see Nicol's reply). If we use `[:]` for unpacking, we can
trivially add slicing to that. If we have sliced unpacking, we can
trivially declare slicing to also work on parameter packs.

Do we also need other ways to manipulate parameter packs? Maybe. What I
don't see is why that should preclude unpacking.

>> I don't see how these slicing proposals are of any help, yet this
>> is a very real use case for metaprogramming.

That's... nice. Many of the example uses for unpacking and even slicing
*do not* involve metaprogramming. Do you have such examples for
reversing and sorting?

--
Matthew

Louis Dionne

unread,
Feb 16, 2016, 3:21:29 PM2/16/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Monday, 15 February 2016 21:10:52 UTC-5, Nicol Bolas wrote:
> On Monday, February 15, 2016 at 6:22:24 PM UTC-5, Louis Dionne wrote:
> > Hi,
> >
> > I'd like to quickly chime in to drop a link to Boost.Hana [1] (which I'm the
> > author of, for full disclosure). There seems to be quite a bit of discussion
> > about adding language features to manipulate packs and tuples, when a lot of
> > this could be done in a library. Hana's purpose is specifically to manipulate
> > tuples (and more generally heterogeneous containers) by providing std-like
> > algorithms to operate on them.
> >
> > Reading the comments here, I just don't see the need for a new language
> > feature for manipulating parameter packs. Instead, I think we need proper
> > standard library features to manipulate tuples with a high level of
> > abstraction.
>
> That's like saying, "Why do we need lambdas in the language?
> We have Boost.Lambda!"
>
> Indeed, I seem to recall Boost.Lambda being one of the impetuses for getting
> language-based lambdas. BLL was basically proof that you couldn't do lambdas
> as a library. It said, "Look, this is the best the language can do as is:
> here's what you get, here's what you have to do to implement it, here's how
> ugly user-code looks, and here are all of the places where it breaks down".

It's significantly different in the level of cumbersomeness of using
Boost.Lambda vs language lambdas, and using Boost.Hana vs what you're
proposing. Language-level lambdas are an obvious relief because using
Boost.Lambda is a PITA. As you'll hopefully see below, the status quo
is workable if you use a proper library.


> In that regard, libraries like Fusion and Hana are perfect examples of why
> tuple unpacking needs to be a language feature.
>
> Show me the Hana code for this:
>
> outer(inner([:]tpl)...)
>
> This calls `inner` on each element of the tuple, then pipes the result into
> the call to `outer`. It works exactly like parameter packs too, so if `tpl`
> were a pack, you just drop the `[:]` syntax and it works.

With Hana, you'd write

    hana::unpack(tpl, hana::on(inner, outer));

or you could also write

    hana::unpack(tpl, [](auto ...x) { return outer(inner(x)...); });

Basically, `unpack` is just `std::apply` but with the arguments reversed.


> Show me the Hana code for this:
>
> auto x = inner([:]tpl) + ...;
>
> This simply calls a function on each element of the tuple and takes the sum
> of the results. Again, it works like parameter packs, so it reuses existing
> knowledge.

You could write

    auto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});

or equivalently

    auto x = hana::fold_left(tpl, [](auto a, auto b) {
        return a + inner(b);
    });

`hana::fold_left` is just like `std::accumulate`, and `hana::transform` is
just like `std::transform`. To me, the fact that we're using algorithms that
we already know from runtime programming is a good thing, whereas your
proposed notation requires yet another special thing to learn. Of course,
your proposed syntax wins here because it was designed precisely for these
use cases, but I hope you'll agree that a library-based solution is nowhere
near the ugliness of good old Boost.Lambda expressions.


> Oh, and show me the Hana code for this:
>
> struct Data
> {
>   int i;
>   float f;
>   double d;
> };
>
> Data d = ...;
> outer(inner([:]d)...);
>
> It's the same as the first example, only using an aggregate.

Ah! That's a good one! Here's how you would write it:

    BOOST_HANA_DEFINE_STRUCT(Data,
        (int, i),
        (float, f),
        (double, d)
    );

    Data d = ...;
    hana::unpack(hana::members(d), hana::on(inner, outer));

Really, the only cumbersome thing here is the definition of the struct.
And I do agree that we need a proper way of introspecting user defined types,
and __this is certainly not the job for a library__. However, if we designed
a proper way of introspecting user-defined types, it would then be very easy
to plug this into the customization point of a library. The challenge of
bringing compile-time introspection to C++ is obviously something that needs
to be done, but I think that's out of the scope of the current discussion.


> [...]
>
> > If properly designed, that could be much more flexible than
> > a language feature in the long term, when we realize that we're missing
> > something else. Just to give you a glimpse: how would you reverse a parameter
> > pack? How would you sort a parameter pack based on a compile-time predicate?
> > I don't see how these slicing proposals are of any help, yet this is a very
> > real use case for metaprogramming.
> >
> > Instead, I think we need to carefully design a STL for metaprogramming (with
> > customization points where it makes sense), and then let users build on top
> > of that. And if you're worried about compile-times being too long with a
> > library-based approach, this can be tackled with a few well-chosen compiler
> > intrinsics (see this article [2]).
>
> So instead of having language support for unpacking tuples, you want
> language support for... some low-level stuff that can be used to build
> a library?
>
> No thanks; I'll take the simple and easy-to-use feature over the huge and
> complex STL-like thing.

The problem I see is that to get rid of a more general library solution that
you deem too complex, you propose adding a language feature that will only
tackle a subproblem. You're basically offloading complexity onto the language
itself, which I think is harmful.



On Tuesday, 16 February 2016 11:07:30 UTC-5, Matthew Woehlke wrote:
> On 2016-02-15 21:10, Nicol Bolas wrote:
> > On Monday, February 15, 2016 at 6:22:24 PM UTC-5, Louis Dionne wrote:
> >> Reading the comments here, I just don't see the need for a new language
> >> feature for manipulating parameter packs. Instead, I think we need proper
> >> standard library features to manipulate tuples with a high level of
> >> abstraction.
> >
> > That's like saying, "Why do we need lambdas in the language? We have
> > Boost.Lambda!"
> > [...]
> >
> > Show me the Hana code for [many examples]:
>
> Wow... thanks, Nicol! Exactly what I would have said, only better :-).
>
> >> If properly designed, that could be much more flexible than
> >> a language feature in the long term, when we realize that we're missing
> >> something else. Just to give you a glimpse: how would you reverse a
> >> parameter pack? How would you sort a parameter pack based on a compile-time
> >> predicate?
>
> *Simple* unpacking solves a basic problem that is hard to solve as a
> library solution (see previous discussion at
>
>   foo([:]tl1..., [:]tl2...);

You could write

    hana::unpack(hana::concat(tl1, tl2), foo);

Sure, it suffers some limitations explored in your link above, but frankly
I would call out these limitations as being academic for the most part.
Definitely not something that justifies a language feature on itself, IMHO.


> [...]
> >> I don't see how these slicing proposals are of any help, yet this
> >> is a very real use case for metaprogramming.
>
> That's... nice. Many of the example uses for unpacking and even slicing
> *do not* involve metaprogramming. Do you have such examples for
> reversing and sorting?

I guess that's just a misunderstanding on what exactly is to be considered
metaprogramming. As for use cases, sorting can be useful if you e.g. have
computations with compile-time dependencies and want to run them in the right
order. You sort them at compile-time and then execute the computations. Or
it could also be sorting types by alignment as a runtime optimization.
Sorting is arguably more useful than reversing in my experience, but others
like `find_if` are even more important yet we don't have any good way of
doing it.


Let it be clear that I understand that the problems you're talking about for
unpacking are real, and we need a solution (language or library-based). My
discomfort lies in the fact that I'd like to see a carefully designed system
that tackles more than just unpacking and slicing tuples, but that has a wider
scope and unifying vision. And if unpacking/slicing ends up needing a language
feature in this __designed__ system, then I'll be all for it. However, what I
think we need to avoid is to introduce yet another short sighted feature that
will quickly reach its limitations, and that we'll then need to patch with
another short sighted feature later on.

Regards,
Louis Dionne

Matthew Woehlke

unread,
Feb 16, 2016, 4:11:36 PM2/16/16
to std-pr...@isocpp.org
(FYI, whatever you used to post your last message just butchered the
quoting. And the line wrapping on your own text isn't much better.)

On 2016-02-16 15:21, Louis Dionne wrote:
> On Monday, 15 February 2016 21:10:52 UTC-5, Nicol Bolas wrote:
>> Oh, and show me the Hana code for this:
>>
>> struct Data
>> {
>> int i;
>> float f;
>> double d;
>> };
>>
>> Data d = ...;
>> outer(inner([:]d)...);
>
> Ah! That's a good one! Here's how you would write it:
>
> BOOST_HANA_DEFINE_STRUCT(Data,

Oops. That right there is a no-go. Redefining the type is right out, as
most likely the type is not something that can be directly controlled.
(Even if it was, uglifying the API like this? No, thanks...)

This is where I think we're having a communication issue. The syntax
you're objecting to is mostly related to fold expressions... which *are
already part of the language*.

Please, by all means, submit proposals to make fold expressions /
dealing with parameter packs better :-).

The "meat" of the general unpacking proposal is turning a tuple-like
into a parameter pack. The above suggests that hana doesn't solve that
problem.
> useful than reversing in my experience, but otherslike `find_if` are
> even more important yet we don't have any good way of doing it.

Again, all of that sounds a) very obscure, and b) like code that gets
written once, buried in a library, and never looked at again.

Unpacking is something that would be useful to me *all the time*.

> Let it be clear that I understand that the problems you're talking
> about for unpacking are real, and we need a solution (language or
> library-based). My discomfort lies in the fact that I'd like to see
> a carefully designed system that tackles more than just unpacking and
> slicing tuples, but that has a wider scope and unifying vision. And
> if unpacking/slicing ends up needing a language feature in this
> __designed__ system, then I'll be all for it. However, what I think
> we need to avoid is to introduce yet another short sighted feature
> that will quickly reach its limitations, and that we'll then need to
> patch with another short sighted feature later on.

Perfect is the enemy of good. My... "concern with your concern", if you
will, is that in trying to build this grand cathedral, we either end up
with no solution at all, or something that makes simple tasks
impractically obtuse.

I tend to think the analogy to lambdas is apt... a language solution may
not be *necessary* in the strict sense, but it's sufficiently *useful*
as to be worthwhile. (Same with P0144, BTW...)

And again, the basic unpacking case (no slicing, just turn a tuple-like
into a parameter pack) allows us to, with a very simple and straight
forward language trick, combine the problem domains of parameter packs
and contained value sequences (i.e. "tuple-likes") so that the same
tools can be applied to both. (I'm certainly willing to entertain that
*those* tools could benefit from a well designed system for working with
parameter packs. But we already *have* parameter packs, so unless you
manage to throw those out entirely, the *worst* I see happening is that
you make `[:]` superfluous. If the slicing form is extended to directly
work on parameter packs, at worst we have a language tool for something
that could be done with a library tool.)

Addition of two integers could be done using a library function rather
than a built-in operator, also... but it's something that happens often
enough that having a language feature (built-in addition operator) is
worthwhile for the improved readability.

At any rate... write a paper. Because this grand cathedral you envision
isn't going to build itself. If you're convinced that there is a better
solution, *show us* that better solution :-).

--
Matthew

Louis Dionne

unread,
Feb 16, 2016, 6:13:17 PM2/16/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Tuesday, 16 February 2016 16:11:36 UTC-5, Matthew Woehlke wrote:
(FYI, whatever you used to post your last message just butchered the
quoting. And the line wrapping on your own text isn't much better.)

I posted my reply from the Google Groups web page, and both the quoting
and the line wrapping look fine there. I'm not sure what you're referring to.
 

On 2016-02-16 15:21, Louis Dionne wrote:
> On Monday, 15 February 2016 21:10:52 UTC-5, Nicol Bolas wrote:
>> Oh, and show me the Hana code for this:
>>
>> struct Data
>> {
>>   int i;
>>   float f;
>>   double d;
>> };
>>
>> Data d = ...;
>> outer(inner([:]d)...);
>
> Ah! That's a good one! Here's how you would write it:
>
>     BOOST_HANA_DEFINE_STRUCT(Data,

Oops. That right there is a no-go. Redefining the type is right out, as
most likely the type is not something that can be directly controlled.
(Even if it was, uglifying the API like this? No, thanks...)

You don't need to redefine the type, since the above macro actually
defines the type itself. If you don't have control over the type, you can
also use BOOST_HANA_ADAPT_STRUCT to adapt an existing struct.
But all of this is completely irrelevant, since I myself said that these macros
were nothing but workarounds for the lack of proper introspection facilities,
which need to be built into the language. This is obviously neither where
we disagree nor the subject of this discussion.
 

This is where I think we're having a communication issue. The syntax
you're objecting to is mostly related to fold expressions... which *are
already part of the language*.

Please, by all means, submit proposals to make fold expressions /
dealing with parameter packs better :-).

The "meat" of the general unpacking proposal is turning a tuple-like
into a parameter pack. The above suggests that hana doesn't solve that
problem.

Did you read my code examples? The above shows that it can be done
with reasonable ease using hana::unpack.
Well, I just showed you a prototype for such a "grand cathedral"; Hana.
It's not like I'm shovelling abstract nonsense around. I'm not trying to
standardize Hana, but I think more awareness about what can be done
is important if you're going to change the language, which is why I
shared my experience with you.

To make it back to the proposal, here's what I would find much more
useful than simple unpacking and slicing, and that could actually be
used to build more complex tuple algorithms. If we could take a tuple
and unpack the elements at arbitrary indices, that would be useful.
For example, disregarding the notation,

auto tuple = std::make_tuple('0', '1', '2', '3', '4', '5');
f
(tuple[1, 0, 3, 3]); //equivalent to f('1', '0', '3', '3')

Or, equivalently, if we could slice a tuple with an arbitrary `std::index_sequence`,
that would be neat. The reason is that getting not-necessarily-adjacent elements
in a tuple is a very basic operation required to implement many other more
complex algorithms, like `remove_if`, `reverse`, and even `sort`. Indeed, the
idea to implement these algorithms is that we take the input tuple, then compute
the indices that we want in the resulting tuple at compile-time (using constexpr
computations or normal template metaprogramming), and then generate an
index_sequence holding those indices. Finally, we extract the subsequence of
elements at those indices to create a new tuple. That gives us a runtime complexity
of O(1), and most computations can be carried in constexpr-world, which can be
faster than template metaprogramming. In my experience, being able to extract
an arbitrary subsequence of a tuple is far more useful than being able to extract
only consecutive elements. Do you see a way your proposal could accommodate this?

Regards,
Louis Dionne

Nicol Bolas

unread,
Feb 16, 2016, 6:19:00 PM2/16/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

Boost.Lambda is workable too... for some definition of "workable".

My point is not that you cannot do it. My point is what your code looks like afterwards.

Hana code only looks reasonable to people experienced in Hana (and preferably with some functional background). The tuple unpacking code looks reasonable to C++ programmers experienced in C++ (variadic templates).

I'll break down your examples to show you.

> In that regard, libraries like Fusion and Hana are perfect examples of why
> tuple unpacking needs to be a language feature.
>
> Show me the Hana code for this:
>
> outer(inner([:]tpl)...)
>
> This calls `inner` on each element of the tuple, then pipes the result into
> the call to `outer`. It works exactly like parameter packs too, so if `tpl`
> were a pack, you just drop the `[:]` syntax and it works.

With Hana, you'd write

    hana::unpack(tpl, hana::on(inner, outer));

I have no idea what that means. I can guess at what `unpack` does, but "on" is completely opaque to me. I'd have to look that up in documentation to figure out what it's doing.

Any C++11 programmer who has reasonable experience with variadic templates can at least take a guess at what `outer(inner([:]tpl)...)` is doing.

I know what a function call looks like. I know what nested function calls look like. `hana::on(inner, outer)` doesn't look anything like that.

Also, I never said that `inner` or `outer` were each a single functions. What happens if they're overloaded? Indeed, given the circumstances (calling `inner` on each type), I imagine that it would be impossible for `inner` to not be overloaded or a template function. Either way, this breaks. Do we need to have lifting syntax for overloads (which to be fair, is a thing we need in general)?

What if `inner` or `outer` is a member function? Does the user now have to whip out `std::bind`?

Also, there are performance questions. Will the indirect call through `hana::on` prevent useful things like inlining?

or you could also write

    hana::unpack(tpl, [](auto ...x) { return outer(inner(x)...); });

Whenever your library equivalent to a 1-liner language feature includes "introduce a Lambda", you have lost in terms of code comprehensibility. On the other hand, it does fix (most) of the functionality problems outlined above.

But here's the #1 reason why the library solution is the wrong solution: you wrote it wrong. You forgot to `std::forward` your arguments. And you forgot to use `decltype(auto)` for the return value. Both of which are needed to be exactly equivalent to the above code. Without the `decltype(auto)`, if `outer` returned a reference of some kind, it could provoke an unwanted copy.

So it really needed to be:

hana::unpack(tpl, [](auto ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(auto)>(x)...)); });

I fail to see how this could be considered anywhere nearly as easy to understand.

Basically, `unpack` is just `std::apply` but with the arguments reversed.


> Show me the Hana code for this:
>
> auto x = inner([:]tpl) + ...;
>
> This simply calls a function on each element of the tuple and takes the sum
> of the results. Again, it works like parameter packs, so it reuses existing
> knowledge.

You could write

    auto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});

And for people who natively read right to left, this would probably be decent. But that's not how the rest of C++ works.

or equivalently

    auto x = hana::fold_left(tpl, [](auto a, auto b) {
        return a + inner(b);
    });

Again you forgot to forward the arguments and return values properly. Not to mention, you turn a simple one-liner into a multi-line statement.
 
`hana::fold_left` is just like `std::accumulate`, and `hana::transform` is
just like `std::transform`. To me, the fact that we're using algorithms that
we already know from runtime programming is a good thing, whereas your
proposed notation requires yet another special thing to learn.

No, it requires learning exactly one special thing: the syntax to turn a tuple into a parameter pack. After that, you're just using existing parameter pack features. Which you need to know anyway, since that's a feature that we're getting one way or another.

Also, if `fold_left` is "just like `std::accumulate`"... why is it called `fold_left`?

Of course,
your proposed syntax wins here because it was designed precisely for these
use cases, but I hope you'll agree that a library-based solution is nowhere
near the ugliness of good old Boost.Lambda expressions.

Actually no. I'd argue that Boost.Lambda is more comprehensible by comparison. While it is certainly extremely bizarre and has a number of pitfalls, once you get used to the idea of a value effectively transforming an expression into a function, it's not so bad for simple cases.

Hana by comparison requires massive effort for the simplest of cases. While I'm sure that it's great for complex metaprogramming, doing something as simple as the cases we've outlined requires a lot of domain knowledge.

That's why I say that Hana would make a good supplement to the language feature. Let the language handle 80+% of all cases; the library can handle the rest.
 
> Oh, and show me the Hana code for this:
>
> struct Data
> {
>   int i;
>   float f;
>   double d;
> };
>
> Data d = ...;
> outer(inner([:]d)...);
>
> It's the same as the first example, only using an aggregate.

Ah! That's a good one! Here's how you would write it:

    BOOST_HANA_DEFINE_STRUCT(Data,
        (int, i),
        (float, f),
        (double, d)
    );

So... how do I put member functions in `Data`?

    Data d = ...;
    hana::unpack(hana::members(d), hana::on(inner, outer));

Really, the only cumbersome thing here is the definition of the struct.

Then we have very different definitions of "cumbersome".
 

I'm not saying that we shouldn't have (something like) Hana. What I'm saying is that, for most use cases, Hana is far more painful to use than having the ability to turn a tuple into a parameter pack. Library facilities for doing complex tuple operations are good.

But basic "for each element of a tuple" operations deserve to be a first-class language feature. They simplify uses of tuples in 80% of cases. For the other 20%, the library solution is available.

You don't use a machete to do surgery. But you don't go into the jungle with just a scalpel either.

Nicol Bolas

unread,
Feb 16, 2016, 6:29:54 PM2/16/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Tuesday, February 16, 2016 at 6:13:17 PM UTC-5, Louis Dionne wrote:
On Tuesday, 16 February 2016 16:11:36 UTC-5, Matthew Woehlke wrote:
This is where I think we're having a communication issue. The syntax
you're objecting to is mostly related to fold expressions... which *are
already part of the language*.

Please, by all means, submit proposals to make fold expressions /
dealing with parameter packs better :-).

The "meat" of the general unpacking proposal is turning a tuple-like
into a parameter pack. The above suggests that hana doesn't solve that
problem.

Did you read my code examples? The above shows that it can be done
with reasonable ease using hana::unpack.

We have very different ideas about what constitutes "reasonable". Your code is functional, but it is not something I'd like to write or to code review. Reasoning about your code requires in-depth knowledge of what all those functions mean, are, and do.

Reasoning about `...` based code can be complex too; don't get me wrong. But it's only complex in complex cases. In simple cases, it's simple.

Hana is always complicated.


Behold the power of the elipsis!

make_tuple(get<[:]index_sequence<1, 0, 3, 3>()>(tpl)...)

All this requires is a `constexpr` version of `get<N>(index_sequence)`. And of course an `index_sequence` that is a literal type.

Is that good enough for you?

Louis Dionne

unread,
Feb 16, 2016, 6:57:59 PM2/16/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
I disagree, for I wouldn't have figured it out myself. There's no point in arguing over this.

 
I know what a function call looks like. I know what nested function calls look like. `hana::on(inner, outer)` doesn't look anything like that.

Also, I never said that `inner` or `outer` were each a single functions. What happens if they're overloaded? Indeed, given the circumstances (calling `inner` on each type), I imagine that it would be impossible for `inner` to not be overloaded or a template function. Either way, this breaks. Do we need to have lifting syntax for overloads (which to be fair, is a thing we need in general)?

A lifting syntax would be nice, but otherwise you can use the other approach I suggested.

 
What if `inner` or `outer` is a member function? Does the user now have to whip out `std::bind`?

Also, there are performance questions. Will the indirect call through `hana::on` prevent useful things like inlining?

Any decent optimizer will get rid of this. If you trust your optimizer enough to optimize away lambdas
in higher-order std algorithms, then you don't have a problem. However, the idea of wildly unpacking
tuples into functions does bring a potential issue. What happens when a function is called with a large
number of arguments performance-wise? If performance is important, it might be better to pass the
actual tuple instead of its unpacked representation, but that would call for a benchmark.
 
or you could also write

    hana::unpack(tpl, [](auto ...x) { return outer(inner(x)...); });

Whenever your library equivalent to a 1-liner language feature includes "introduce a Lambda", you have lost in terms of code comprehensibility. On the other hand, it does fix (most) of the functionality problems outlined above.

You're drawing an arbitrary line without any argument for this. Actually,
I tend to prefer writing things more explicitly like above than using
nested ... expansions when things get complex, for I think the different
rules for ... expansion can be confusing.
 

But here's the #1 reason why the library solution is the wrong solution: you wrote it wrong. You forgot to `std::forward` your arguments. And you forgot to use `decltype(auto)` for the return value. Both of which are needed to be exactly equivalent to the above code. Without the `decltype(auto)`, if `outer` returned a reference of some kind, it could provoke an unwanted copy.

So it really needed to be:

hana::unpack(tpl, [](auto ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(auto)>(x)...)); });

I fail to see how this could be considered anywhere nearly as easy to understand.

Fair enough. But if we're going to be pedantic, let's be pedantic for real. What you wanted
to write is
hana::unpack(tpl, [](auto&& ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(x)>(x))...); });

With this out of the way, I will argue that you actually don't need to write the above, except
in rare cases where the tuple is potentially a rvalue. Instead, in most cases, you'd just
have to write

hana::unpack(tpl, [](auto& ...x) -> decltype(auto) { return outer(inner(x)...); });

which is slightly less verbose.


Basically, `unpack` is just `std::apply` but with the arguments reversed.


> Show me the Hana code for this:
>
> auto x = inner([:]tpl) + ...;
>
> This simply calls a function on each element of the tuple and takes the sum
> of the results. Again, it works like parameter packs, so it reuses existing
> knowledge.

You could write

    auto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});

And for people who natively read right to left, this would probably be decent. But that's not how the rest of C++ works.

Wtf? How is this different from writing 

auto x = ranges::accumulate(ranges::transformed(...), std::plus<>{});



or equivalently

    auto x = hana::fold_left(tpl, [](auto a, auto b) {
        return a + inner(b);
    });

Again you forgot to forward the arguments and return values properly. Not to mention, you turn a simple one-liner into a multi-line statement.

Same argument as above. And the fact that I broke the statements into multiple
lines to make it more readable is a feature. Honestly, I think this (and especially
the transform/fold_left variant) is more readable than the variant with ... expansions.
Of course, your version is more terse, but too terse is not good either.

Let's stop arguing over petty details. You want to hear it? Of course your solution
is better for those use cases, because it was designed with those in mind!
But it is also much more limited, and the point I'm trying to make is that from
the point of view of a metaprogramming library writer, this proposal, in its
current form, misses the goal just like fold expressions did.


`hana::fold_left` is just like `std::accumulate`, and `hana::transform` is
just like `std::transform`. To me, the fact that we're using algorithms that
we already know from runtime programming is a good thing, whereas your
proposed notation requires yet another special thing to learn.

No, it requires learning exactly one special thing: the syntax to turn a tuple into a parameter pack. After that, you're just using existing parameter pack features. Which you need to know anyway, since that's a feature that we're getting one way or another.

Also, if `fold_left` is "just like `std::accumulate`"... why is it called `fold_left`?

This naming is consistent with other choices made in the library.
 

Of course,
your proposed syntax wins here because it was designed precisely for these
use cases, but I hope you'll agree that a library-based solution is nowhere
near the ugliness of good old Boost.Lambda expressions.

Actually no. I'd argue that Boost.Lambda is more comprehensible by comparison. While it is certainly extremely bizarre and has a number of pitfalls, once you get used to the idea of a value effectively transforming an expression into a function, it's not so bad for simple cases.

Hana by comparison requires massive effort for the simplest of cases. While I'm sure that it's great for complex metaprogramming, doing something as simple as the cases we've outlined requires a lot of domain knowledge.

Have you tried using it? Have you tried writing equivalent code yourself?
 

That's why I say that Hana would make a good supplement to the language feature. Let the language handle 80+% of all cases; the library can handle the rest.
 
> Oh, and show me the Hana code for this:
>
> struct Data
> {
>   int i;
>   float f;
>   double d;
> };
>
> Data d = ...;
> outer(inner([:]d)...);
>
> It's the same as the first example, only using an aggregate.

Ah! That's a good one! Here's how you would write it:

    BOOST_HANA_DEFINE_STRUCT(Data,
        (int, i),
        (float, f),
        (double, d)
    );

So... how do I put member functions in `Data`?

I actually wrote this wrong. The correct way would have been

struct Data {
    BOOST_HANA_DEFINE_STRUCT(Data,
        (int, i),
        (float, f),
        (double, d)
    );

    // You can put whatever you want here
};

But again, this is largely irrelevant to the ongoing discussion and I would be more
than happy to have a proper language feature that would allow me to remove these
crufty macros.

Louis Dionne

unread,
Feb 16, 2016, 7:07:30 PM2/16/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Tuesday, 16 February 2016 18:29:54 UTC-5, Nicol Bolas wrote:
On Tuesday, February 16, 2016 at 6:13:17 PM UTC-5, Louis Dionne wrote:
On Tuesday, 16 February 2016 16:11:36 UTC-5, Matthew Woehlke wrote:
This is where I think we're having a communication issue. The syntax
you're objecting to is mostly related to fold expressions... which *are
already part of the language*.

Please, by all means, submit proposals to make fold expressions /
dealing with parameter packs better :-).

The "meat" of the general unpacking proposal is turning a tuple-like
into a parameter pack. The above suggests that hana doesn't solve that
problem.

Did you read my code examples? The above shows that it can be done
with reasonable ease using hana::unpack.

We have very different ideas about what constitutes "reasonable". Your code is functional, but it is not something I'd like to write or to code review. Reasoning about your code requires in-depth knowledge of what all those functions mean, are, and do.

Reasoning about `...` based code can be complex too; don't get me wrong. But it's only complex in complex cases. In simple cases, it's simple.

Hana is always complicated.

I doubt you're speaking out of experience here. I can't really defend
myself against this since I wrote the library, but I think it's plain BS.
No, unfortunately it's not. The one thing that's really interesting with having a language
feature to slice a tuple is that the compiler might implement this more efficiently than
doing a dumb syntactic translation and then instantiating the `get<>` functions for real.
With what you outline above, the compiler has to instantiate the `get<>` function for
the index_sequence, and then for the tuple. This is a problem for compile-times.

Basically, what I need is simple. I need a very fast (at compile-time) way to get the
elements at arbitrary indices inside a tuple. If your proposal does not give me this,
it's of no use to me (as a metaprogramming library writer, that is). Otherwise, then
you just solved one of my long standing problems.

Arthur O'Dwyer

unread,
Feb 16, 2016, 8:42:31 PM2/16/16
to std-pr...@isocpp.org
On Tue, Feb 16, 2016 at 3:13 PM, Louis Dionne <ldio...@gmail.com> wrote:
> On Tuesday, 16 February 2016 16:11:36 UTC-5, Matthew Woehlke wrote:
>> On 2016-02-16 15:21, Louis Dionne wrote:
>> > On Monday, 15 February 2016 21:10:52 UTC-5, Nicol Bolas wrote:
>> >> Oh, and show me the Hana code for this:
>> >>
>> >> struct Data
>> >> {
>> >>   int i;
>> >>   float f;
>> >>   double d;
>> >> };
>> >>
>> >> Data d = ...;
>> >> outer(inner([:]d)...);
>> >
>> > Ah! That's a good one! Here's how you would write it:
>> >
>> >     BOOST_HANA_DEFINE_STRUCT(Data,
>>
>> Oops. That right there is a no-go. [...]
>
> [...] If you don't have control over the type, you can

> also use BOOST_HANA_ADAPT_STRUCT to adapt an existing struct.
> But all of this is completely irrelevant, since I myself said that these macros
> were nothing but workarounds for the lack of proper introspection facilities,
> which need to be built into the language. This is obviously neither where
> we disagree nor the subject of this discussion.

Right. The idea that a POD struct should be tuple-like was never part of this discussion before, and I don't see how it'd be possible without a lot of unproposed language support for introspection/reflection, *and* personally I currently think it would be a bad idea. Certainly it's a distraction from the point of the discussion.

FWIW, guys, I'm pretty sure Louis has at least twice the real-world metaprogramming experience of all three of us put together. IMO you shouldn't be quick to dismiss anything he has to say on the subject.  (Carefully considered dismissal is still okay. ;))


>> > On Tuesday, 16 February 2016 11:07:30 UTC-5, Matthew Woehlke wrote:
>> >> On Monday, February 15, 2016 at 6:22:24 PM UTC-5, Louis Dionne wrote:
>> >>> I don't see how these slicing proposals are of any help, yet this
>> >>> is a very real use case for metaprogramming.
>> >>
>> >> That's... nice. Many of the example uses for unpacking and even slicing
>> >> *do not* involve metaprogramming. Do you have such examples for
>> >> reversing and sorting?
>> >
>> > I guess that's just a misunderstanding on what exactly is to be
>> > considered metaprogramming. As for use cases, sorting can be useful
>> > if you e.g. have computations with compile-time dependencies and
>> > want to run them in the right order. You sort them at compile-time
>> > and then execute the computations. Or it could also be sorting types
>> > by alignment as a runtime optimization. Sorting is arguably more
>> > useful than reversing in my experience, but otherslike `find_if` are
>> > even more important yet we don't have any good way of doing it.

Matthew's Python-inspired [:] notation would support reversal and strided access easily enough (just as Python does). My tilde-notation would not.

Sorting, filtering, etc would still be the domain of libraries like Hana, no matter what, because they're intrinsically operations on the data items (e.g. the types in a typelist), rather than on the indices. So we can't get away from the need for libraries; the question Louis is raising, IMHO, is whether given the need for a library, we could get away with using that library even for the "simple" stuff. My kneejerk reaction is "no, pack-expansions are much much better than Hana for the simple stuff"; but I'm not 100% sure, never having played with Hana.


>> > Let it be clear that I understand that the problems you're talking
>> > about for unpacking are real, and we need a solution (language or
>> > library-based). My discomfort lies in the fact that I'd like to see
>> > a carefully designed system that tackles more than just unpacking and
>> > slicing tuples, but that has a wider scope and unifying vision. And
>> > if unpacking/slicing ends up needing a language feature in this
>> > __designed__ system, then I'll be all for it. However, what I think
>> > we need to avoid is to introduce yet another short sighted feature
>> > that will quickly reach its limitations, and that we'll then need to
>> > patch with another short sighted feature later on.

Hear, hear. (I said a similar thing earlier, not about the need for a language feature at all, but about the need for the language feature to hook directly into the names std::get and std::tuple_element_t. Just because I don't know how to do it right doesn't mean nobody knows.)

[...]
> To make it back to the proposal, here's what I would find much more
> useful than simple unpacking and slicing, and that could actually be
> used to build more complex tuple algorithms. If we could take a tuple
> and unpack the elements at arbitrary indices, that would be useful.
[...]

> faster than template metaprogramming. In my experience, being able to extract
> an arbitrary subsequence of a tuple is far more useful than being able to extract
> only consecutive elements. Do you see a way your proposal could accommodate
> this?

Nicol has shown how to do it with tuple-unpacking, assuming that index_sequence is tuple-like:
    tie(get<index_sequence<1, 0, 3, 3>()~>(tpl)...)
In fact, this shorter refinement would also work (out of the box), because make_tuple is constexpr:
    tie(get<make_tuple(1, 0, 3, 3)~>(tpl)...)

If we assume that our constexpr tuple of indices is named "indices", then:

    return forward_as_tuple(get<indices~>(tpl)...);

would do the trick perfectly, I think. (I won't bet money on it though. :))
(Thanks, Nicol, for that neat trick — I wouldn't have thought of it myself!)

–Arthur

Nicol Bolas

unread,
Feb 16, 2016, 10:28:33 PM2/16/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Tuesday, February 16, 2016 at 6:57:59 PM UTC-5, Louis Dionne wrote:
On Tuesday, 16 February 2016 18:19:00 UTC-5, Nicol Bolas wrote:
On Tuesday, February 16, 2016 at 3:21:29 PM UTC-5, Louis Dionne wrote:
or you could also write

    hana::unpack(tpl, [](auto ...x) { return outer(inner(x)...); });

Whenever your library equivalent to a 1-liner language feature includes "introduce a Lambda", you have lost in terms of code comprehensibility. On the other hand, it does fix (most) of the functionality problems outlined above.

You're drawing an arbitrary line without any argument for this.

... yes. Code quality is always arbitrary. Some people claim that this is a good, easy-to-understand piece of code:

char * strcpy(char *strDest, const char *strSrc)
{
   
assert(strDest!=NULL && strSrc!=NULL);
   
char *temp = strDest;
   
while(*strDest++ = *strSrc++);
   
return temp;
}

Everyone has their own tastes. But... Concepts TS has 3 different and increasingly brief syntaxes for declaring a constrained template for a reason. In N3701, Stroustrup et. al. defended this by saying:

> Do not confuse the familiar with the simple. The proposed syntax is readable and parsable. We considered “louder”, more verbose notations, but did not find them consistently better than what is described here.

Brevity has value.

Actually,
I tend to prefer writing things more explicitly like above than using
nested ... expansions when things get complex, for I think the different
rules for ... expansion can be confusing.

Welcome to the point of the whole idea.

In this example, what we're trying to do is to call `inner` for each element of the tuple, then pass the results as parameters to `outer`. To you, this is something that "gets complex".

The purpose of the language feature is so that it isn't complex anymore. By making it a language feature, we take something that was "complex" and make it simple. That's the point.

Your library solution is much like `std::enable_if`. Yes, it gives you a consistent tool for invoking SFINAE. But I bet you'd rather be using Concepts.

`enable_if` makes SFINAE doable, but it requires that you express your condition in an unnatural way. Concepts makes SFINAE trivial. Same here: Hana makes tuple manipulation doable, but requires that you write your code in unnatural ways.

The principle difference is that there's nothing `enable_if` can do that concepts can't. Whereas there's a lot that Hana can do which tuple expansion can't. But that alone doesn't mean that it isn't worth being a language feature.

Lambdas were supposed to obsolete std::bind too. Yet there are still some valid uses for it.

But here's the #1 reason why the library solution is the wrong solution: you wrote it wrong. You forgot to `std::forward` your arguments. And you forgot to use `decltype(auto)` for the return value. Both of which are needed to be exactly equivalent to the above code. Without the `decltype(auto)`, if `outer` returned a reference of some kind, it could provoke an unwanted copy.

So it really needed to be:

hana::unpack(tpl, [](auto ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(auto)>(x)...)); });

I fail to see how this could be considered anywhere nearly as easy to understand.

Fair enough. But if we're going to be pedantic, let's be pedantic for real. What you wanted
to write is
hana::unpack(tpl, [](auto&& ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(x)>(x))...); });

With this out of the way, I will argue that you actually don't need to write the above, except
in rare cases where the tuple is potentially a rvalue. Instead, in most cases, you'd just
have to write

hana::unpack(tpl, [](auto& ...x) -> decltype(auto) { return outer(inner(x)...); });

which is slightly less verbose.

My point was not just that it was verbose. My point is that it is both verbose and easy to get wrong, which we both demonstrated (though at least my error would have shown up in the compiler ;) ).

Basically, `unpack` is just `std::apply` but with the arguments reversed.


> Show me the Hana code for this:
>
> auto x = inner([:]tpl) + ...;
>
> This simply calls a function on each element of the tuple and takes the sum
> of the results. Again, it works like parameter packs, so it reuses existing
> knowledge.

You could write

    auto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});

And for people who natively read right to left, this would probably be decent. But that's not how the rest of C++ works.

Wtf? How is this different from writing 

auto x = ranges::accumulate(ranges::transformed(...), std::plus<>{});


You will never catch me writing that either.

or equivalently

    auto x = hana::fold_left(tpl, [](auto a, auto b) {
        return a + inner(b);
    });

Again you forgot to forward the arguments and return values properly. Not to mention, you turn a simple one-liner into a multi-line statement.

Same argument as above. And the fact that I broke the statements into multiple
lines to make it more readable is a feature. Honestly, I think this (and especially
the transform/fold_left variant) is more readable than the variant with ... expansions.
Of course, your version is more terse, but too terse is not good either.

It's not really that it's terse; that's not what attracts me to tuple expansion. What matters most to me is that the code looks as much like normal code ought to look.

This is also what repels me from your ranges example.

Most programmers know what `outer(inner(value))` does. They can understand that by inspection, and its meaning is clear. Most programmers understand what `inner(value) + inner(value2)` means.

Hana code is not obvious, not to someone who isn't familiar with template metaprogramming and such techniques.

While an unsuspecting programmer may not understand exactly what the `...` and `[:]` parts mean, they can still look at `outer(inner([:]value)...)` and see that `inner` will be called, followed by `outer`. It carries the same physical structure and code layout of the simple and obvious case. It may be more complex under the hood, but the user is not exposed to it.

When looking at `hana::on(inner, outer)`, they have absolutely no idea what that means. Not without looking up the docs. There is no intuitive grasp of what's going on.

So the value is more than "just" terseness. The value is that the code's structure remains intact. You're not exchanging a call to `+` with `std::plus<>`. You're not altering the overall order and nature of the code.

Let's stop arguing over petty details. You want to hear it? Of course your solution
is better for those use cases, because it was designed with those in mind!
But it is also much more limited, and the point I'm trying to make is that from
the point of view of a metaprogramming library writer, this proposal, in its
current form, misses the goal just like fold expressions did.

And my overall point is that what you want was never the goal to begin with. You are denigrating a proposal for not solving a problem that it was never intended to solve. The problem it is solving is making tuples work like parameter pack expansion, so as to be able to more effectively access data out of tuples in useful ways.

Arbitrary tuple transformations was never the goal. That's a legitimate and useful problem domain. But that's not what this is intended to handle. The fact that this syntax can indeed handle some of that via clever usage of the syntax is merely a fortunate coincidence.

C++11 already has a construct that is tuple-like in its nature: parameter packs. We're just expanding it to work with actual tuples.

Vicente J. Botet Escriba

unread,
Feb 17, 2016, 1:36:52 AM2/17/16
to std-pr...@isocpp.org, mwoehlk...@gmail.com
Hi,

I agree here with Louis, this is orthogonal. There is a proposal for defaulting tuple-like access [1] that should help here.

Vicente

[1] https://github.com/viboes/std-make/blob/master/doc/proposal/reflection/P0197R0.md

Sam Kellett

unread,
Feb 17, 2016, 4:40:26 AM2/17/16
to std-pr...@isocpp.org
On 17 February 2016 at 03:28, Nicol Bolas <jmck...@gmail.com> wrote:
 [snip]

It's not really that it's terse; that's not what attracts me to tuple expansion. What matters most to me is that the code looks as much like normal code ought to look.

This is also what repels me from your ranges example.

Most programmers know what `outer(inner(value))` does. They can understand that by inspection, and its meaning is clear. Most programmers understand what `inner(value) + inner(value2)` means.

Hana code is not obvious, not to someone who isn't familiar with template metaprogramming and such techniques.

While an unsuspecting programmer may not understand exactly what the `...` and `[:]` parts mean, they can still look at `outer(inner([:]value)...)` and see that `inner` will be called, followed by `outer`. It carries the same physical structure and code layout of the simple and obvious case. It may be more complex under the hood, but the user is not exposed to it.

with all due respect you seem to be taking your own opinion on this and applying it to all. while that could be ok on it's own it's clashes with the fact that you also take louis' opinion on this and applying it to just him.

do you have a study / survey that confirms this? my personal opinion is that hana's is much much much more obvious (the lambda version of the example specifically), in no short reason because it's interface is based on the existing standard library. why do we need two syntax's in one language when we can do it all with one?
 
When looking at `hana::on(inner, outer)`, they have absolutely no idea what that means. Not without looking up the docs. There is no intuitive grasp of what's going on.

again you kinda need proof that this isn't also true for your example. have you shown it to people blind (without knowledge of the problem domain) and have they been able to deduce what it means?

also yours appears to be inherently ungoogle-able. assuming i don't understand either syntax, for louis' i type into google 'c++ hana::unpack', what do i type to find the reference pages for yours?

Louis Dionne

unread,
Feb 17, 2016, 11:16:16 AM2/17/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
I'm sorry if you perceived my comments as denigration, for that was never my goal. I know the power
of getting a different perspective, especially one that has been thought out, and that is what I wanted
to share with you. We're all trying to achieve the same thing here; a language that allows us to express
ourselves more easily. We're disagreeing on the way to get there, and you seem to be impermeable to
my ideas. It's fine, but my work here is done for I don't feel like something good can come out of more
discussion like what we've been having.


The problem it is solving is making tuples work like parameter pack expansion, so as to be able to more effectively access data out of tuples in useful ways.

When you say "useful ways", I gather that you're saying "ways that are useful for myself". Indeed, I'm
precisely saying that accessing tuples in the way you propose isn't useful to me, but you say that this
isn't the goal. I hope you hit the needs of the standard committee members right on the spot, otherwise
they might find it difficult to modify _the freaking language_ for your use case.

Regards,
Louis Dionne

Matthew Woehlke

unread,
Feb 17, 2016, 12:05:37 PM2/17/16
to std-pr...@isocpp.org
On 2016-02-16 19:07, Louis Dionne wrote:
> On Tuesday, 16 February 2016 18:29:54 UTC-5, Nicol Bolas wrote:
>> *Behold the power of the elipsis!*
>>
>> make_tuple(get<[:]index_sequence<1, 0, 3, 3>()>(tpl)...)
>>
>> All this requires is a `constexpr` version of `get<N>(index_sequence)`.
>> And of course an `index_sequence` that is a literal type.
>>
>> Is that good enough for you?
>
> No, unfortunately it's not. The one thing that's really interesting
> with having a language feature to slice a tuple is that the compiler
> might implement this more efficiently than doing a dumb syntactic
> translation and then instantiating the `get<>` functions for real.

Uh... no... not really. The definition of "tuple-like" is "can be
accessed via get<N>". The only time you'll see this happen more
efficiently is if `get<N>` is implicitly provided (a la P0197). In which
case, it should be at least as efficient as your "ideal".

> With what you outline above, the compiler has to instantiate the
> `get<>` function for the index_sequence, and then for the tuple.

Except it *has* to do that. Otherwise, it doesn't know how to access the
elements of the tuple(-like)!

> Basically, what I need is simple. I need a very fast (at
> compile-time) way to get the elements at arbitrary indices inside a
> tuple.

Then you're SOL. You'll only get this - *maybe* - if you use an
aggregate instead of a std::tuple or any old tuple-like. (If you want to
propose somehow guaranteeing that get<N>(std::tuple) is fast, that'd be
fine too, but is orthogonal.)

...or just complain to your compiler vendor; I see no reason this can be
done with an intrinsic as a QoI matter. (Oh, and likewise for
std::index_range while you're at it.)

--
Matthew

Matthew Woehlke

unread,
Feb 17, 2016, 12:13:50 PM2/17/16
to std-pr...@isocpp.org
On 2016-02-16 20:42, Arthur O'Dwyer wrote:
> The idea that a POD struct should be tuple-like was never part of
> this discussion before, and I don't see how it'd be possible without a lot
> of unproposed language support for introspection/reflection,

Have you *read* P0197¹?


https://github.com/viboes/std-make/blob/master/doc/proposal/reflection/P0197R0.md
- the papers in the Feb 2016 mailing don't seem to be avaliable yet.)

> *and* personally I currently think it would be a bad idea.

Why?

> [...] assuming that index_sequence is tuple-like:

I would certainly *hope* it is! :-)

> (Thanks, Nicol, for that neat trick — I wouldn't have thought of it myself!)

Likewise on both counts!

--
Matthew

Matthew Woehlke

unread,
Feb 17, 2016, 12:20:10 PM2/17/16
to std-pr...@isocpp.org
On 2016-02-16 18:13, Louis Dionne wrote:
> On Tuesday, 16 February 2016 16:11:36 UTC-5, Matthew Woehlke wrote:
>> On 2016-02-16 15:21, Louis Dionne wrote:
>>> BOOST_HANA_DEFINE_STRUCT(Data,
>>
>> Oops. That right there is a no-go. Redefining the type is right out, as
>> most likely the type is not something that can be directly controlled.
>> (Even if it was, uglifying the API like this? No, thanks...)
>
> You don't need to redefine the type, since the above macro actually
> defines the type itself. If you don't have control over the type, you can
> also use BOOST_HANA_ADAPT_STRUCT to adapt an existing struct.

What about something that satisfies "tuple-like" but does NOT have
public members? That's critical for many, if not most, of my use cases.

I think you're missing the question here. Hana may work on std::tuple
and aggregates (with some additional gymnastics, or if we get
reflection). Will it (or can it be made to) work on *any type with get<N>*?

> To make it back to the proposal, here's what I would find much more
> useful than simple unpacking and slicing, and that could actually be
> used to build more complex tuple algorithms. If we could take a tuple
> and unpack the elements at arbitrary indices, that would be useful.
> For example, disregarding the notation,
>
> auto tuple = std::make_tuple('0', '1', '2', '3', '4', '5');
> f(tuple[1, 0, 3, 3]); //equivalent to f('1', '0', '3', '3')

Although I haven't been noting it recently (and in fact, have
reconsidered whether it should be allowed), my original idea for slicing
does in fact allow this:

f([1,0,3,3]tuple...);

More crudely, even with simplified slicing, you can definitely write:

f([1]tuple, [0]tuple, [3]tuple, [3]tuple);

(Of course, you can also do that today; the single item case is just
short-hand for `get<1>(tuple), ...`.)

> Or, equivalently, if we could slice a tuple with an arbitrary
> `std::index_sequence`,

In *theory*, integer literals can be replaced with any constexpr integer
expression. (For obvious reasons, the compiler has to know the actual
indices at compile time.) This could probably be extended to
index_sequence as well. Trouble comes due to possible confusion with
lambda syntax, however, especially if the tuple-like is an inline
initializer list.

If we made '@' part of the language, this could be easily addressed:

[@expr]tuple; // 'expr' is any constexpr integer or integer sequence

...or perhaps there is some other syntax that is not overly clunky but
avoids ambiguity with existing constructs. (Slicing with literals
doesn't have this problem, as literals are not valid captures and so are
not ambiguous with lambdas.)

Alternatively, do it as Nicol showed :-).

> Do you see a way your proposal could accommodate this?

Sure; see above.

--
Matthew

Matthew Woehlke

unread,
Feb 17, 2016, 12:43:57 PM2/17/16
to std-pr...@isocpp.org
On 2016-02-16 18:57, Louis Dionne wrote:
> What happens when a function is called with a large number of
> arguments performance-wise? If performance is important, it might be
> better to pass the actual tuple instead of its unpacked
> representation, but that would call for a benchmark.

I see a couple cases here:

1. For whatever reason, you have arguments to a non-template function in
a tuple-like, and want to use them to invoke a function. (Might happen
with RPC's. *Definitely* happens when dealing with linear algebra
vectors, when the function takes N floats/doubles, but you have a
vector-type of some sort.)

2. You need to pass a tuple-like to a function that could take a tuple.

3. You need to pass a bunch of values to a template function that will
do something with each value.

For #1, it's not really relevant; the reason you're unpacking in the
first place is almost certainly because you can't change the API being
called to take the struct in the first place, or because there is no
reason to do so. Performance is probably not an issue, and you likely
aren't dealing with "many" values anyway.

For #2, passing a tuple instead of a bunch of values may require
constructing a tuple from a tuple-like. That may indeed be worthwhile,
but you may need unpacking to construct the tuple in the first place.

For #3, you may need unpacking to make use of the tuple that was
received. You almost certainly will if the function takes an arbitrary
tuple-like.

I definitely agree with your point here. I just don't see it as a good
argument why unpacking isn't needed (although it may affect *where*
unpacking is used).

> On Tuesday, 16 February 2016 18:19:00 UTC-5, Nicol Bolas wrote:
>> On Tuesday, February 16, 2016 at 3:21:29 PM UTC-5, Louis Dionne wrote:
>>> You could write
>>>
>>> auto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});
>>
>> And for people who natively read right to left, this would probably be
>> decent. But that's not how the rest of C++ works.
>
> Wtf? How is this different from writing
>
> auto x = ranges::accumulate(ranges::transformed(...), std::plus<>{});

You missed the point. It's different from:

auto x = inner([:]tpl) + ...;

...which is MUCH more terse, and (to me, anyway) very easy to read and
understand.

> the point I'm trying to make is that from the point of view of a
> metaprogramming library writer, this proposal, in its current form,
> misses the goal just like fold expressions did.

As Nicol also pointed out, improved metaprogramming was never an
explicit goal.

The first, most important goal is to be able to write:

foo([:]tpl...);

Secondary goals are being able to slice in unpacking assignments, and
being able to use a tuple-like in parameter pack based fold expressions,
especially `expr << ... << [:]tpl;`.

If it also helps metaprogramming, that's an added bonus. That's not 90+%
of C++ users, though.

--
Matthew

Matthew Woehlke

unread,
Feb 17, 2016, 1:07:05 PM2/17/16
to std-pr...@isocpp.org
On 2016-02-17 04:40, Sam Kellett wrote:
> my personal opinion is that hana's is much much much more obvious
> (the lambda version of the example specifically), in no short reason
> because it's interface is based on the existing standard library. why
> do we need two syntax's in one language when we can do it all with
> one?

...because parameter packs "don't exist" in a sense. Accordingly, it is
*necessary* that there exists a language syntax to instruct the compiler
how to turn them into real code.

As I stated elsewhere, a lot of the ranting and railing I see here is
directed not at unpacking, but at *fold expressions*. That's borderline
off topic, and I'm not necessarily opposed to improving fold
expressions. (Though I also agree with Nicol that fold expressions are
at least as understandable, and in some cases more so, than hana.)

The point about familiarity here is apt. The hana syntax is obtuse to
anyone not familiar with it. Fold expressions... do have their own
problems, but IMHO are easier to grasp for someone who is a total novice
with both.

> also yours appears to be inherently ungoogle-able. assuming i don't
> understand either syntax, for louis' i type into google 'c++ hana::unpack',
> what do i type to find the reference pages for yours?

https://www.google.com/search?q=c%2B%2B+fold+expressions

--
Matthew

Miro Knejp

unread,
Feb 17, 2016, 4:30:28 PM2/17/16
to ISO C++ Standard - Future Proposals
I somehow can't shed the feeling that what we really need here is support for multiple return values.

Just imagine for a moment that the following is valid:
int, float, double foo();
void bar(int, float, double);
bar(foo());
auto {a, b, c} = foo(); // borrowed from P0144R1

having this ability makes writing tuple unpacking trivial
auto tpl = make_tuple(1, 2.f, 3.0);
bar(unpack(tpl));
bar(unpack<2, 1, 0>(tpl));

template<class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, index_sequence_for<Ts...>());
}
template<size_t... Is, class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, make_index_sequence<Is...>());
}
template<size_t From, size_t To, class... Ts>
auto... unpack_range(tuple<Ts...> x)
{
return unpack_impl<From>(x, make_index_sequence<To - From>);
}
template<size_t Offset, class... Ts, size_t... Is>
auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>())
{
return get<Is + Offset>(x)...;
}
(and yes, I did omit stuff like rvalue-refs, forward() etc. as they are only distracting and I am sure everyone here can fill in the details themselves)

I feel like this opens up the doors to many more possibilities than just the unpacking syntax alone as discussed in this thread. The syntax may not be as terse, but to me "unpack(x)" feels easier to understand and teach than "[:]x". One might consider changing "unpack_range<a, b>(x)" into "unpack<range<a, b>>(x)", making "unpack" even more potent as a commonly understood tool (and allows things like unpack<range<0, 2>, 4>(x) getting the indices 0, 1, 4).

Other examples:
template<class Ts...>
auto tail(tpl<Ts...> x)
{
return make_tuple(unpack_range<1, sizeof...(Ts)>(x));
}
template<size_t Index, class Ts...>
auto... split(tpl<Ts...> x)
{
return make_tuple(unpack<range<0, Index>>(x)), make_tuple(unpack<range<Index, sizeof...(Ts)>>(x));
}
Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper etc, this should also make the example tail(ref(tpl1)) = tpl2 work.

I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here. I guess it comes down to which of the two is more general purpose and/or has the lesser impact on the language. I just felt like mentioning this and maybe get more support for multiple return values if people are interested as the rest emerges naturally from it and the topic of multiple return values resurfaces regularly.

Matthew Woehlke

unread,
Feb 17, 2016, 5:22:45 PM2/17/16
to std-pr...@isocpp.org
On 2016-02-17 16:30, Miro Knejp wrote:
> I somehow can't shed the feeling that what we really need here is support
> for multiple return values.

I won't say we *don't* need MRV's. However...

> having this ability makes writing tuple unpacking trivial:
>
> template<class... Ts> auto... unpack(tuple<Ts...> x);

First off, you meant:

template <typename T> auto... unpack(T);

You mentioned issues with MRV's, so I won't go there except to
acknowledge the point. Although I will note that one of those issues is
creation of something that *looks* like a single value that is actually
multiple values, in a way that's non-obvious (more so than parameter
packs, since the expansion is automatic). In fact, I might venture so
far as to wonder if all the objections against my original proposal
wouldn't come into play? (Do you support fold expressions with MRV's,
for instance?)

TBH, as much as I argued this previously, I have to say that at this
point I'm inclined to see U2PP¹, and possibly P0222, as the best
solution to MRV's. I can't think offhand of any problems this *doesn't*
solve, and it a) retains the ability to pass around MRV's as single
objects, because they still *are* single objects, and b) adds the power
of fold expressions to all tuple-likes.

(¹ Unpacking <to> Parameter Packs)

> I feel like this opens up the doors to many more possibilities than just
> the unpacking syntax alone as discussed in this thread. The syntax may not
> be as terse, but to me "unpack(x)" feels easier to understand and teach
> than "[:]x". One might consider changing "unpack_range<a, b>(x)" into
> "unpack<range<a, b>>(x)", making "unpack" even more potent as a commonly
> understood tool (and allows things like unpack<range<0, 2>, 4>(x) getting
> the indices 0, 1, 4).

...but as Nicol already showed, you can do this with `[:]` also:

get<[:]{[:]range<a, b>..., 4}>(x)...

...so I am not convinced that this "opens up the doors to many more
possibilities". Rather, it seems like just a different way to achieve
the same possibilities. (Granted, my version is not quite as pretty as
`unpack<range<a, b>>(x)`, but `[:]` trades brevity of more common cases
for verbosity of less common cases.)

Do you have an example of something MRV's could achieve that U2PP can't?

> Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper
> etc, this should also make the example tail(ref(tpl1)) = tpl2 work.

How would this be different from / better than:

std::tie([1:]tpl1...) = tpl2;

...?

--
Matthew

Miro Knejp

unread,
Feb 17, 2016, 6:36:54 PM2/17/16
to std-pr...@isocpp.org
Am 17.02.2016 um 23:22 schrieb Matthew Woehlke:
> On 2016-02-17 16:30, Miro Knejp wrote:
>> I somehow can't shed the feeling that what we really need here is support
>> for multiple return values.
> I won't say we *don't* need MRV's. However...
>
>> having this ability makes writing tuple unpacking trivial:
>>
>> template<class... Ts> auto... unpack(tuple<Ts...> x);
> First off, you meant:
>
> template <typename T> auto... unpack(T);
No, I didn't. The example I provided was for std::tuple explicitly to
demonstrate how MRVs come into the picture. People can obviously
customize unpack() in addition to get<>() using ADL as necessary.
>
> You mentioned issues with MRV's, so I won't go there except to
> acknowledge the point. Although I will note that one of those issues is
> creation of something that *looks* like a single value that is actually
> multiple values, in a way that's non-obvious (more so than parameter
> packs, since the expansion is automatic). In fact, I might venture so
> far as to wonder if all the objections against my original proposal
> wouldn't come into play? (Do you support fold expressions with MRV's,
> for instance?)
>
> TBH, as much as I argued this previously, I have to say that at this
> point I'm inclined to see U2PP¹, and possibly P0222, as the best
> solution to MRV's. I can't think offhand of any problems this *doesn't*
> solve, and it a) retains the ability to pass around MRV's as single
> objects, because they still *are* single objects, and b) adds the power
> of fold expressions to all tuple-likes.
auto t = make_tuple(foo()).
There is your "ability to pass around MRV's as single objects".
make_tuple() and unpack() become symmetric operations (so maybe a pack()
is in order...). (do you have a link to P0222? I couldn't find anything
with that number)

In my head MRVs are a transient construct only existent as the type of a
call expression. If you need to capture it assign the values to
varaiables (like in P0144R1) or forward as parameters to another call
expression (like make_tuple). Under this assumption, that MRVs are their
own kind of entity, folding becomes possible as (0 + ... + foo()) if
their special nature is considered. Therefore folding a tuple becomes (0
+ ... + unpack(tpl)).
>
> (¹ Unpacking <to> Parameter Packs)
>
>> I feel like this opens up the doors to many more possibilities than just
>> the unpacking syntax alone as discussed in this thread. The syntax may not
>> be as terse, but to me "unpack(x)" feels easier to understand and teach
>> than "[:]x". One might consider changing "unpack_range<a, b>(x)" into
>> "unpack<range<a, b>>(x)", making "unpack" even more potent as a commonly
>> understood tool (and allows things like unpack<range<0, 2>, 4>(x) getting
>> the indices 0, 1, 4).
> ...but as Nicol already showed, you can do this with `[:]` also:
>
> get<[:]{[:]range<a, b>..., 4}>(x)...
That was not the point. I didn't say the syntax proposed in this thread
can't do it. But I'd rather see unpack<range<a, b>>(x) than a line with
*8 consecutive punctuation characters*. Some people are already
challenged by []{} :)
>
> ...so I am not convinced that this "opens up the doors to many more
> possibilities". Rather, it seems like just a different way to achieve
> the same possibilities.
If all you see is tuples then MRVs don't add anything, that is true. The
syntax from here is dedicated to tuple-like things *only*. Whereas MRVs
are useful outside the scope of tuple-like things but can be used to
implement all the problems (as far as I can tell) presented in this
discussion as a library extension. I can't judge how much more/less of
an impact specifying the unpacking syntax is compared to MRVs to be able
to tell which had a lower barrier of acceptance in the committee, since
in the context of this thread alone MRVs don't add anything new.
> (Granted, my version is not quite as pretty as
> `unpack<range<a, b>>(x)`, but `[:]` trades brevity of more common cases
> for verbosity of less common cases.)
>
> Do you have an example of something MRV's could achieve that U2PP can't?
Again, no, since "U2PP" is only useful in the context of tuples whereas
MRVs are not constrained to the tuple use case only. People have
repeatedly asked for the ability to return multiple values for all kinds
of use cases.
>
>> Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper
>> etc, this should also make the example tail(ref(tpl1)) = tpl2 work.
> How would this be different from / better than:
>
> std::tie([1:]tpl1...) = tpl2;
>
> ...?
It uses words that actually describe what it does.

Without MRVs you cannot hide "[1:]tpl1..." effortlessly behind a named
function. *Every single time* someone needs to unpack the tail of a
tuple they have to write out the same sequence of punctuation. DRY
anyone? Even languages with this kind of syntax come shipped with
utility functions like tail() because they're more descriptive. In order
to hide it inside a function you have to return a tuple (maybe
containing references) and then use the awkward inversion of "apply(f,
something(x))" when using the result. The benefits of the unpacking
syntax are completely lost to the user at that point. Compare that to
"f(something(x))" and tell me which one you'd prefer to write and read.

If you're asking whether it performs better then nobody can tell unless
you have a hacked compiler to measure compile times and look at the
disassembly.

Sam Kellett

unread,
Feb 18, 2016, 3:56:53 AM2/18/16
to std-pr...@isocpp.org
so assuming that you *don't* understand the code you are looking at. how on earth did you deduce what they are called?

Vicente J. Botet Escriba

unread,
Feb 18, 2016, 4:40:41 AM2/18/16
to std-pr...@isocpp.org
Le 17/02/2016 22:30, Miro Knejp a écrit :
I somehow can't shed the feeling that what we really need here is support for multiple return values.

I don't know if mandatory for the subject on this thread, but yes, I believe we need MRV, but I don't know exactly what this would mean yet.

Just imagine for a moment that the following is valid:
int, float, double foo();
void bar(int, float, double);
bar(foo());
auto {a, b, c} = foo(); // borrowed from P0144R1

Could you show how would you define foo() to return MRV?
    return 0, 1.0, 2.0;

or

    return 0 1.0 2.0;

Or?

IIUC, the single things you can do with a MRV is to use it in a call expression and in this case each one of the values is associated to an argument, and
structure binding. However structure binding is associated to a tuple-like interface. Does it means that a MRV thing would have a tuple-like access interface (get<I>(mrv))?

If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for some a1,a2,a3

it should also be expanded in the context of
auto {a, b, c} = foo();

and so we will need that

auto {a, b, c} = a1, a2, a3;

But
P0144R1 doesn't support it, neither P0222R0. 

Or would the user need to pack it before?

auto {a, b, c} = make_tuple(foo());


IMO a MRV, or whatever we call the result of a MRV function, either
* is always unpacked and needs some sort of pack function (make-tuple) in some cases or
* is packed and we needs some sort of unpack function. I will not be against the use of an operator to unpack a MRV

bar(*foo());



having this ability makes writing tuple unpacking trivial
auto tpl = make_tuple(1, 2.f, 3.0);
bar(unpack(tpl));
bar(unpack<2, 1, 0>(tpl));

template<class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, index_sequence_for<Ts...>());
}
template<size_t... Is, class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, make_index_sequence<Is...>());
}
template<size_t From, size_t To, class... Ts>
auto... unpack_range(tuple<Ts...> x)
{
return unpack_impl<From>(x, make_index_sequence<To - From>);
}
template<size_t Offset, class... Ts, size_t... Is>
auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>())
{
return get<Is + Offset>(x)...;
}
(and yes, I did omit stuff like rvalue-refs, forward() etc. as they are only distracting and I am sure everyone here can fill in the details themselves)

Here you introduce something additional. auto... as a deduced MRV.

I suspect to be inline with the way we return here  (there is no use of ',' to separate the arguments, the foo() function should return as

    return 0 1.0 2.0;

I feel like this opens up the doors to many more possibilities than just the unpacking syntax alone as discussed in this thread. The syntax may not be as terse, but to me "unpack(x)" feels easier to understand and teach than "[:]x". One might consider changing "unpack_range<a, b>(x)" into "unpack<range<a, b>>(x)", making "unpack" even more potent as a commonly understood tool (and allows things like unpack<range<0, 2>, 4>(x) getting the indices 0, 1, 4).

Other examples:
template<class Ts...>
auto tail(tpl<Ts...> x)
{
return make_tuple(unpack_range<1, sizeof...(Ts)>(x));
}
template<size_t Index, class Ts...>
auto... split(tpl<Ts...> x)
{
return make_tuple(unpack<range<0, Index>>(x)), make_tuple(unpack<range<Index, sizeof...(Ts)>>(x));
}
Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper etc, this should also make the example tail(ref(tpl1)) = tpl2 work.

I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.
Well, as there is not yet a proposal for MRV we need to consider yours here, to see if is something that could be acceptable. What I mean is that we can not compare two solutions to a specific problem until we have concrete solutions and we can see the advantages and liabilities for both solutions. Maybe you prefer to start a new thread.

I guess it comes down to which of the two is more general purpose and/or has the lesser impact on the language. I just felt like mentioning this and maybe get more support for multiple return values if people are interested as the rest emerges naturally from it and the topic of multiple return values resurfaces regularly.

I'm not yet for or against any solution, we need concrete proposals containing more details.

Vicente

Miro Knejp

unread,
Feb 18, 2016, 10:37:15 AM2/18/16
to std-pr...@isocpp.org


Am 18.02.2016 um 10:40 schrieb Vicente J. Botet Escriba:
Le 17/02/2016 22:30, Miro Knejp a écrit :
I somehow can't shed the feeling that what we really need here is support for multiple return values.

I don't know if mandatory for the subject on this thread, but yes, I believe we need MRV, but I don't know exactly what this would mean yet.
Just imagine for a moment that the following is valid:
int, float, double foo();
void bar(int, float, double);
bar(foo());
auto {a, b, c} = foo(); // borrowed from P0144R1

Could you show how would you define foo() to return MRV?
    return 0, 1.0, 2.0;

or

    return 0 1.0 2.0;

Or?
There are examples further down in my post. Besides any syntax presented here is just a strawman so don't get lost in the details too much.


IIUC, the single things you can do with a MRV is to use it in a call expression and in this case each one of the values is associated to an argument, and
structure binding. However structure binding is associated to a tuple-like interface. Does it means that a MRV thing would have a tuple-like access interface (get<I>(mrv))?
No. If you want to return multiple values in something with a tupe-like interface, well then use tuple and P0144 binding. There's also a proposal in SG14 to synthesize get<>() for public fields, making any old struct compatible with P0144. It is important to keep the multiple return values separate in a special entity to allow for the compiler magic that unpacks it effortlessly into a list of parameters.
If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for some a1,a2,a3

it should also be expanded in the context of
auto {a, b, c} = foo();

and so we will need that

auto {a, b, c} = a1, a2, a3;
No, since "a1, a2, a3" is a comma-expression and the binding only applies to the result of the expression "a3". If P0144 doesn't consider this special case then it will become impossible to add it later. Functions returning multiple values return a special kind of entity that is transparent to the user and allows unpacking into arguments and can only exist as a temporary. You cannot reference it directly, you cannot decltype() it (same as with parameter packs) and you cannot spell its type. It is not a different syntax for a comma-expression for which we don't need anythign new. What we need is special treatment for "this function returns multiple values and I can pass them as separate arguments to call expressions". We already have a solution if it's not a function result but parameters: parameter packs.


But
P0144R1 doesn't support it, neither P0222R0. 
Where is this P0222R0 and why is Google unable to find it? :(


Or would the user need to pack it before?

auto {a, b, c} = make_tuple(foo());


IMO a MRV, or whatever we call the result of a MRV function, either
* is always unpacked and needs some sort of pack function (make-tuple) in some cases or
* is packed and we needs some sort of unpack function. I will not be against the use of an operator to unpack a MRV

bar(*foo());
My concept treats multiple return values as unpacked, since we already have solutions for packed values: tuple and other classes. MRVs can only be used to
(1) be unpacked as parameters to a call expression
(2) be a direct subexpression in a return statement
(3) be unpacked in a fold expression
(4) be captured into variables by the syntax in P0144

I know (4) is redundant because of (1) but I feel like "auto {a, b, c} = foo()" is simply too convenient to not have it supported compared to "auto {a, b, c} = std::make_tuple(foo())" (which also needs an include). With this basic functionality we can implement everything else mentioned in this thread as a library extension using make_tuple() and unpack(). But since unpack() doesn't depend on implementation-defined magical compiler intrinsics anyone can write their own special snowflake if they so desire.

Unpacking may require the unpack operator to allow the difference between outer(inner(unpack(x)...)) and outer(inner(unpack(x))...) as mentioned earlier. That is a detail that needs to be discussed.



having this ability makes writing tuple unpacking trivial
auto tpl = make_tuple(1, 2.f, 3.0);
bar(unpack(tpl));
bar(unpack<2, 1, 0>(tpl));

template<class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, index_sequence_for<Ts...>());
}
template<size_t... Is, class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, make_index_sequence<Is...>());
}
template<size_t From, size_t To, class... Ts>
auto... unpack_range(tuple<Ts...> x)
{
return unpack_impl<From>(x, make_index_sequence<To - From>);
}
template<size_t Offset, class... Ts, size_t... Is>
auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>())
{
return get<Is + Offset>(x)...;
}
(and yes, I did omit stuff like rvalue-refs, forward() etc. as they are only distracting and I am sure everyone here can fill in the details themselves)

Here you introduce something additional. auto... as a deduced MRV.
I suspect to be inline with the way we return here  (there is no use of ',' to separate the arguments, the foo() function should return as

    return 0 1.0 2.0;
Remember this is strawman syntax, but here goes anyway: "auto..." is to tell the compiler that "return a, b, c;" (as is the result of a parameter pack expansion) does not use the comma operator but actually returns multiple values. If the compiler expects only a single value then "return a, b, c;" only returns the result of evaluating "c" (or whatever crazy person overloaded the comma operator with). This is only needed in deduced return types and "auto..." is arbitrarily chosen because it is currently ill-formed. If you spell out all return types akin to something like "int, int, int foo()" then "return a, b, c;" is unambiguous. Both are means to not break existing code.

I feel like this opens up the doors to many more possibilities than just the unpacking syntax alone as discussed in this thread. The syntax may not be as terse, but to me "unpack(x)" feels easier to understand and teach than "[:]x". One might consider changing "unpack_range<a, b>(x)" into "unpack<range<a, b>>(x)", making "unpack" even more potent as a commonly understood tool (and allows things like unpack<range<0, 2>, 4>(x) getting the indices 0, 1, 4).

Other examples:
template<class Ts...>
auto tail(tpl<Ts...> x)
{
return make_tuple(unpack_range<1, sizeof...(Ts)>(x));
}
template<size_t Index, class Ts...>
auto... split(tpl<Ts...> x)
{
return make_tuple(unpack<range<0, Index>>(x)), make_tuple(unpack<range<Index, sizeof...(Ts)>>(x));
}
Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper etc, this should also make the example tail(ref(tpl1)) = tpl2 work.

I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.
Well, as there is not yet a proposal for MRV we need to consider yours here, to see if is something that could be acceptable. What I mean is that we can not compare two solutions to a specific problem until we have concrete solutions and we can see the advantages and liabilities for both solutions. Maybe you prefer to start a new thread.
I haven't given the standard wording any thought yet so whether MRVs are their own thing or parameter packs or whatever is entirely open for debate (in a different thread if there is enough interest).
I guess it comes down to which of the two is more general purpose and/or has the lesser impact on the language. I just felt like mentioning this and maybe get more support for multiple return values if people are interested as the rest emerges naturally from it and the topic of multiple return values resurfaces regularly.

I'm not yet for or against any solution, we need concrete proposals containing more details.

Vicente
--

---
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 18, 2016, 11:19:09 AM2/18/16
to std-pr...@isocpp.org
On 2016-02-17 18:38, Miro Knejp wrote:
> Am 17.02.2016 um 23:22 schrieb Matthew Woehlke:
>> On 2016-02-17 16:30, Miro Knejp wrote:
>>> I somehow can't shed the feeling that what we really need here is
>>> support for multiple return values.
>>>
>>> having this ability makes writing tuple unpacking trivial:
>>>
>>> template<class... Ts> auto... unpack(tuple<Ts...> x);
>>
>> First off, you meant:
>>
>> template <typename T> auto... unpack(T);
>
> No, I didn't. The example I provided was for std::tuple explicitly to
> demonstrate how MRVs come into the picture. People can obviously
> customize unpack() in addition to get<>() using ADL as necessary.

Does assignment unpacking use std::unpack, then? If so, then you've just
changed the customization point. (If not, I object to requiring
different customization points for such similar operations.)

That said, I'm not convinced that std::get isn't the superior
customization point. (Keep in mind I also objected to having to
specialize std::tuple_size, always.)

>> TBH, as much as I argued this previously, I have to say that at this
>> point I'm inclined to see U2PP¹, and possibly P0222, as the best
>> solution to MRV's.
>
> (do you have a link to P0222? I couldn't find anything with that number)

It hasn't been published yet AFAICT, but should be in the Feb 2016
mailing whenever that does get published.

Meanwhile:
https://github.com/mwoehlke/cpp-proposals/blob/master/p0222r0-anonymous-struct-return.rst
(be aware that github strips the markup for standardese edits; check the
raw reST for these, though in this case the edit may be obvious).

> In my head MRVs are a transient construct only existent as the type of a
> call expression. If you need to capture it assign the values to
> varaiables (like in P0144R1) or forward as parameters to another call
> expression (like make_tuple). Under this assumption, that MRVs are their
> own kind of entity, folding becomes possible as (0 + ... + foo()) if
> their special nature is considered. Therefore folding a tuple becomes (0
> + ... + unpack(tpl)).

So... basically, an MRV *is* a parameter pack? In that case, I don't see
much difference between "true MRV's" and U2PP.

>>> Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper
>>> etc, this should also make the example tail(ref(tpl1)) = tpl2 work.
>>
>> How would this be different from / better than:
>>
>> std::tie([1:]tpl1...) = tpl2;
>>
>> ...?
>
> It uses words that actually describe what it does.
>
> Without MRVs you cannot hide "[1:]tpl1..." effortlessly behind a named
> function.

You can't?

auto tail(auto tuple) { return make_tuple([1:]tuple...); }

(Simplified for illustrative purposes; usual caveats about proper
reference handling, forwarding, etc.)

> *Every single time* someone needs to unpack the tail of a
> tuple they have to write out the same sequence of punctuation. DRY
> anyone?

That's like saying "every time someone needs to add two numbers they
have to write '+'". Yes, every time you do something you have to write
something. DRY is when you are writing the same *non-trivial* code
repeatedly. Seven characters (`[1:]...`) is not non-trivial, especially
as your example uses a minimum of six (`tail()`, assuming that you are
`using namespace std;`, which not everyone wants to do!).

> Even languages with this kind of syntax come shipped with
> utility functions like tail() because they're more descriptive.

I could argue that. If I'm not familiar with it, I need to check the
documentation to know if 'tail' means `[1:]` or `[-1]`, because it could
mean either one. With U2PP slicing, there is no such ambiguity. (And
anyway, as shown above, there is nothing stopping us having a
`std::tail()` with U2PP.)

> In order to hide it inside a function you have to return a tuple
> (maybe containing references) and then use the awkward inversion of
> "apply(f, something(x))" when using the result.

Ah... no? Why? Huh?
U2PP makes std::apply superfluous.

> Compare that to "f(something(x))" and tell me which one you'd prefer
> to write and read.

I'm not thrilled that I can't tell there that `something(x)` is a
parameter pack. In that sense, I prefer the clarity of
`f([:]something(x)...)`. (Note that this is also how e.g. Python works.
Despite often being used as the poster child for MRV's, Python actually
has no such thing... just lots of syntax sugar a la P0144 and U2PP that
makes it seem as if it does.)

Oh! Here's a fun point... if we *did* have true MRV's, how long until
people start writing unary operator* as a synonym for unpack? Would that
be a good thing?

--
Matthew

Matthew Woehlke

unread,
Feb 18, 2016, 11:28:30 AM2/18/16
to std-pr...@isocpp.org
On 2016-02-18 11:18, Matthew Woehlke wrote:
> On 2016-02-17 18:38, Miro Knejp wrote:
>> (do you have a link to P0222? I couldn't find anything with that number)
>
> It hasn't been published yet AFAICT, but should be in the Feb 2016
> mailing whenever that does get published.

My bad: http://wg21.link/p0222 (I think this went live within the last
24 hours...)

--
Matthew

Matthew Woehlke

unread,
Feb 18, 2016, 11:54:50 AM2/18/16
to std-pr...@isocpp.org
On 2016-02-18 04:40, Vicente J. Botet Escriba wrote:
> If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for
> some a1,a2,a3
>
> it should also be expanded in the context of
> |
> auto {a, b, c} = foo();
>
> and so we will need that
>
> |auto {a, b, c} = a1, a2, a3;
>
> But |||P0144R1 doesn't support it, neither P0222R0. |

If `foo()` returns a parameter pack (which I think Miro and I agree that
it should), then what we need is:

// 'pack' is any expression which expands to a parameter pack
auto {a, b, c} = {pack...};
auto {a, b, c} = pack; // same as previous line

I would like to see this enhancement regardless; it's almost necessary
if we gain "true MRV's" (TMRV's), and it's an obvious and significant
improvement if we gain sliced U2PP. It's also potentially useful with
neither, in variadic templates. (Unfortunately, P0144R1 doesn't support
either. There is a weak argument against the first given in P0144R1. I
think if we gain either TMRV's (and/)or U2PP, there would be a much
stronger motivation to support braced lists (and parameter packs) in
assignment unpacking.

@Miro, I don't think anything needs to be considered now; if TMRV's are
parameter packs, then there should be no problem adding special handling
of parameter packs to assignment unpacking at some later time. (Worst
case, you'd need to add {}'s, and we're in the braced list special case.)

(I don't think P0222 is relevant here ;-).)

> I suspect to be inline with the way we return here (there is no use of
> ',' to separate the arguments, the foo() function should return as
>
> | return 0 1.0 2.0;|

char*, char* foo() { return "houston" "we" "have" "a" "problem"; }

We need commas :-). (Anyway, not a fruitful discussion, as Miro notes.)

--
Matthew

inkwizyt...@gmail.com

unread,
Feb 18, 2016, 11:55:02 AM2/18/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
When you only have symbols "[:]" and "..."? Try use google on that.
When you know name then you know it and don't need google it to get info what it is.

Matthew Woehlke

unread,
Feb 18, 2016, 12:03:21 PM2/18/16
to std-pr...@isocpp.org
On 2016-02-18 10:39, Miro Knejp wrote:
> There's also a proposal in SG14 to synthesize get<>() for public
> fields, making any old struct compatible with P0144.

Are you talking about http://wg21.link/p0197? Or is there another
proposal we missed?

> Where is this P0222R0 and why is Google unable to find it? :(

It's probably too new for Google to have indexed it yet. (And apparently
I didn't use the document number in any of the threads here to be able
to find those via the number. Sorry... When in doubt, try wg21.link, but
it's possible that only started working since your last message.)

> But since unpack() doesn't depend on implementation-defined magical
> compiler intrinsics anyone can write their own special snowflake if they
> so desire.

Tuple-like via get<> doesn't depend on magical compiler intrinsics any
more or less than TMRV's + unpack(). In both cases, you either provide
your own, or rely on a compiler provided version of either `get<N>` or
`unpack`.

> Unpacking may require the unpack operator to allow the difference
> between outer(inner(unpack(x)...)) and outer(inner(unpack(x))...) as
> mentioned earlier. That is a detail that needs to be discussed.

IMO it should... Above point notwithstanding, I think it would be just
too confusing if TMRV's are... parameter packs with an exception. (Plus
I still tend to think that there should be some indication that you have
multiple values.)

--
Matthew

Vicente J. Botet Escriba

unread,
Feb 18, 2016, 12:06:14 PM2/18/16
to std-pr...@isocpp.org
Le 18/02/2016 16:39, Miro Knejp a écrit :


Am 18.02.2016 um 10:40 schrieb Vicente J. Botet Escriba:
Le 17/02/2016 22:30, Miro Knejp a écrit :
I somehow can't shed the feeling that what we really need here is support for multiple return values.

I don't know if mandatory for the subject on this thread, but yes, I believe we need MRV, but I don't know exactly what this would mean yet.
Just imagine for a moment that the following is valid:
int, float, double foo();
void bar(int, float, double);
bar(foo());
auto {a, b, c} = foo(); // borrowed from P0144R1

Could you show how would you define foo() to return MRV?
    return 0, 1.0, 2.0;

or

    return 0 1.0 2.0;

Or?
There are examples further down in my post. Besides any syntax presented here is just a strawman so don't get lost in the details too much.

As you describe below, these details are important.


IIUC, the single things you can do with a MRV is to use it in a call expression and in this case each one of the values is associated to an argument, and
structure binding. However structure binding is associated to a tuple-like interface. Does it means that a MRV thing would have a tuple-like access interface (get<I>(mrv))?
No. If you want to return multiple values in something with a tupe-like interface, well then use tuple and P0144 binding. There's also a proposal in SG14 to synthesize get<>() for public fields, making any old struct compatible with P0144.
Could you give me a reference, because I'm the author of P0197 - Default tuple-like access and I'm really concerned ?

It is important to keep the multiple return values separate in a special entity to allow for the compiler magic that unpacks it effortlessly into a list of parameters.
If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for some a1,a2,a3

it should also be expanded in the context of
auto {a, b, c} = foo();

and so we will need that

auto {a, b, c} = a1, a2, a3;
No, since "a1, a2, a3" is a comma-expression and the binding only applies to the result of the expression "a3". If P0144 doesn't consider this special case then it will become impossible to add it later.
P0144 can not take in account the MRV you are describing here ;-)
You your proposal, if you write one, should describe how this could work. IN the absence, I believe that the user would need


auto {a, b, c} = make_tuple(foo());

Functions returning multiple values return a special kind of entity that is transparent to the user and allows unpacking into arguments and can only exist as a temporary. You cannot reference it directly, you cannot decltype() it (same as with parameter packs) and you cannot spell its type. It is not a different syntax for a comma-expression for which we don't need anythign new. What we need is special treatment for "this function returns multiple values and I can pass them as separate arguments to call expressions". We already have a solution if it's not a function result but parameters: parameter packs.
We need however to know how to return them.


But
P0144R1 doesn't support it, neither P0222R0. 
Where is this P0222R0 and why is Google unable to find it? :(


Or would the user need to pack it before?

auto {a, b, c} = make_tuple(foo());


IMO a MRV, or whatever we call the result of a MRV function, either
* is always unpacked and needs some sort of pack function (make-tuple) in some cases or
* is packed and we needs some sort of unpack function. I will not be against the use of an operator to unpack a MRV

bar(*foo());
My concept treats multiple return values as unpacked, since we already have solutions for packed values: tuple and other classes. MRVs can only be used to
(1) be unpacked as parameters to a call expression
(2) be a direct subexpression in a return statement
(3) be unpacked in a fold expression
(4) be captured into variables by the syntax in P0144

I'm missing how to build one from N values, bu I see that you describe it below.

I know (4) is redundant because of (1) but I feel like "auto {a, b, c} = foo()" is simply too convenient to not have it supported compared to "auto {a, b, c} = std::make_tuple(foo())" (which also needs an include).
I will not say redundant. If MRV don't provide a syntax to retrieve the multiple returned values, I would say that there is something missing.

With this basic functionality we can implement everything else mentioned in this thread as a library extension using make_tuple() and unpack(). But since unpack() doesn't depend on implementation-defined magical compiler intrinsics anyone can write their own special snowflake if they so desire.

Unpacking may require the unpack operator to allow the difference between outer(inner(unpack(x)...)) and outer(inner(unpack(x))...) as mentioned earlier. That is a detail that needs to be discussed.



having this ability makes writing tuple unpacking trivial
auto tpl = make_tuple(1, 2.f, 3.0);
bar(unpack(tpl));
bar(unpack<2, 1, 0>(tpl));

template<class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, index_sequence_for<Ts...>());
}
template<size_t... Is, class... Ts>
auto... unpack(tuple<Ts...> x)
{
return unpack_impl<0>(x, make_index_sequence<Is...>());
}
template<size_t From, size_t To, class... Ts>
auto... unpack_range(tuple<Ts...> x)
{
return unpack_impl<From>(x, make_index_sequence<To - From>);
}
template<size_t Offset, class... Ts, size_t... Is>
auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>())
{
return get<Is + Offset>(x)...;
}
(and yes, I did omit stuff like rvalue-refs, forward() etc. as they are only distracting and I am sure everyone here can fill in the details themselves)

Here you introduce something additional. auto... as a deduced MRV.
I suspect to be inline with the way we return here  (there is no use of ',' to separate the arguments, the foo() function should return as

    return 0 1.0 2.0;
Remember this is strawman syntax, but here goes anyway: "auto..." is to tell the compiler that "return a, b, c;" (as is the result of a parameter pack expansion) does not use the comma operator but actually returns multiple values. If the compiler expects only a single value then "return a, b, c;" only returns the result of evaluating "c" (or whatever crazy person overloaded the comma operator with). This is only needed in deduced return types and "auto..." is arbitrarily chosen because it is currently ill-formed. If you spell out all return types akin to something like "int, int, int foo()" then "return a, b, c;" is unambiguous. Both are means to not break existing code.
I believe that this will be conflicting.



I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.
Well, as there is not yet a proposal for MRV we need to consider yours here, to see if is something that could be acceptable. What I mean is that we can not compare two solutions to a specific problem until we have concrete solutions and we can see the advantages and liabilities for both solutions. Maybe you prefer to start a new thread.
I haven't given the standard wording any thought yet so whether MRVs are their own thing or parameter packs or whatever is entirely open for debate (in a different thread if there is enough interest).
I'm not talking yet of wording, but of how to use the feature.

Vicente

Miro Knejp

unread,
Feb 18, 2016, 12:44:23 PM2/18/16
to std-pr...@isocpp.org
Am 18.02.2016 um 17:18 schrieb Matthew Woehlke:
> On 2016-02-17 18:38, Miro Knejp wrote:
>> Am 17.02.2016 um 23:22 schrieb Matthew Woehlke:
>>> On 2016-02-17 16:30, Miro Knejp wrote:
>>>> I somehow can't shed the feeling that what we really need here is
>>>> support for multiple return values.
>>>>
>>>> having this ability makes writing tuple unpacking trivial:
>>>>
>>>> template<class... Ts> auto... unpack(tuple<Ts...> x);
>>> First off, you meant:
>>>
>>> template <typename T> auto... unpack(T);
>> No, I didn't. The example I provided was for std::tuple explicitly to
>> demonstrate how MRVs come into the picture. People can obviously
>> customize unpack() in addition to get<>() using ADL as necessary.
> Does assignment unpacking use std::unpack, then? If so, then you've just
> changed the customization point. (If not, I object to requiring
> different customization points for such similar operations.)
>
> That said, I'm not convinced that std::get isn't the superior
> customization point. (Keep in mind I also objected to having to
> specialize std::tuple_size, always.)
If `get()` is the superior customization point you obviously don't need
to customize `unpack()`. However that is a library issue and there is no
point in arguing over the library interface while the necessary language
feature is still undecided.
>
>>> TBH, as much as I argued this previously, I have to say that at this
>>> point I'm inclined to see U2PP¹, and possibly P0222, as the best
>>> solution to MRV's.
>> (do you have a link to P0222? I couldn't find anything with that number)
> It hasn't been published yet AFAICT, but should be in the Feb 2016
> mailing whenever that does get published.
>
> Meanwhile:
> https://github.com/mwoehlke/cpp-proposals/blob/master/p0222r0-anonymous-struct-return.rst
> (be aware that github strips the markup for standardese edits; check the
> raw reST for these, though in this case the edit may be obvious).
Thanks
>
>> In my head MRVs are a transient construct only existent as the type of a
>> call expression. If you need to capture it assign the values to
>> varaiables (like in P0144R1) or forward as parameters to another call
>> expression (like make_tuple). Under this assumption, that MRVs are their
>> own kind of entity, folding becomes possible as (0 + ... + foo()) if
>> their special nature is considered. Therefore folding a tuple becomes (0
>> + ... + unpack(tpl)).
> So... basically, an MRV *is* a parameter pack? In that case, I don't see
> much difference between "true MRV's" and U2PP.
Call it whatever you want, I don't care. At this stage I'm interested in
the functionality not bikeshedding of syntax or classification.
>
>>>> Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper
>>>> etc, this should also make the example tail(ref(tpl1)) = tpl2 work.
>>> How would this be different from / better than:
>>>
>>> std::tie([1:]tpl1...) = tpl2;
>>>
>>> ...?
>> It uses words that actually describe what it does.
>>
>> Without MRVs you cannot hide "[1:]tpl1..." effortlessly behind a named
>> function.
> You can't?
>
> auto tail(auto tuple) { return make_tuple([1:]tuple...); }
>
> (Simplified for illustrative purposes; usual caveats about proper
> reference handling, forwarding, etc.)
Yes, that is how I would implement `tail()` even with MRVs because
`tail()` has the signature `(a_0, a_1, ..., a_n) -> (a_1, ..., a_n)`. A
better name for the example would have been `unpack_tail(tpl)` or
similar, my bad. Basically it needs the signature `(a_0, a_1, ..., a_n)
-> a_1, ..., a_n`.

But yet, those two are not equivalent. I cannot substitute the
expression `[1:]tpl...` with the expression `unpack_tail(tpl)` using
only the tools given in this thread and get the same semantics. Or am I
wrong? That's what I mean with you can't `hide` it. A full substitution
is `[:]unpack_tail(tpl)...` but that is a leaky abstraction because in
order to get the same semantics you have to apply additional operators.
>
>> *Every single time* someone needs to unpack the tail of a
>> tuple they have to write out the same sequence of punctuation. DRY
>> anyone?
> That's like saying "every time someone needs to add two numbers they
> have to write '+'". Yes, every time you do something you have to write
> something. DRY is when you are writing the same *non-trivial* code
> repeatedly. Seven characters (`[1:]...`) is not non-trivial, especially
> as your example uses a minimum of six (`tail()`, assuming that you are
> `using namespace std;`, which not everyone wants to do!).
Calling a function is not a violation of DRY. It's not about writing
"something" or "counting characters", it's about not repeating the same
implementation but give the repeatedly used abstraction or operation a
meaningful name. The triviality doesn't matter. `tail()` is obviously a
very trivial example, but even there I'd prefer a descriptive name
instead of wading through a forest of consecutive punctuation characters
(eight(!) in the previous example) over and over again.

So instead I'd like to hide `[1,5:a,y*3:photon_torpedo()~~^3]tpl...`
behind `federation_unpack(tpl)` which should have the signature `(a_0,
a_1, ..., a_n) -> x, y, z`. It doesn't return a `tuple<x, y, z>` because
it's called "unpack" and the expression I want to substitute with it is
not of type `tuple<x, y, z>` but of `x, y, z`. But an implementation
without MRVs is forced to the signature `(a_0, a_1, ..., a_n) -> (x, z,
y)` which I then have to additionally apply the actual unpacking to.
That's not really abstracting the "unpacking" part, is it? It's as if we
had to dereference references...
>
>> Even languages with this kind of syntax come shipped with
>> utility functions like tail() because they're more descriptive.
> I could argue that. If I'm not familiar with it, I need to check the
> documentation to know if 'tail' means `[1:]` or `[-1]`, because it could
> mean either one. With U2PP slicing, there is no such ambiguity. (And
> anyway, as shown above, there is nothing stopping us having a
> `std::tail()` with U2PP.)
`tail()` is a ubiquitous operation in basically every language that has
some form of builtin list or tuple type. How it is implemented doesn't
usually matter. What does matter is that I don't have to copy the
implementation around, may it be as trivial as it is in the case of
`tail()`. If the C++ committe manages to make `tail()` do something
tremendously different then congratulations on confusing everybody!
>
>> In order to hide it inside a function you have to return a tuple
>> (maybe containing references) and then use the awkward inversion of
>> "apply(f, something(x))" when using the result.
> Ah... no? Why? Huh?
> U2PP makes std::apply superfluous.
Well OK, either `apply(f, [:]something(x)...)` or
`f([:]something(x)...)` are equivalent, and both are leaking the fact
that I actually wanted a list of values but got a tuple instead.
>
>> Compare that to "f(something(x))" and tell me which one you'd prefer
>> to write and read.
> I'm not thrilled that I can't tell there that `something(x)` is a
> parameter pack. In that sense, I prefer the clarity of
> `f([:]something(x)...)`. (Note that this is also how e.g. Python works.
> Despite often being used as the poster child for MRV's, Python actually
> has no such thing... just lots of syntax sugar a la P0144 and U2PP that
> makes it seem as if it does.)
I call that paranoia, as is the case with every new language feature.
Soon we won't be able to tell whether `void foo(X x)` is the definition
of a function template or not. Are you also not going to avoid that? For
how long? But as noted in a previous response to Vicente, the unpack
operator *may* be necessary if we want to allow the different nesting
levels known from parameter pack expansions. I don't like it as it
suffers from the same problems as I described above, and I still hope it
can be omitted for the most common case of immedate unpacking. That may
be wishful thinking though.
>
> Oh! Here's a fun point... if we *did* have true MRV's, how long until
> people start writing unary operator* as a synonym for unpack? Would that
> be a good thing?
>
People define their own operators for DSLs or convenience all the time.
If it gets established as common practice then there's no point in
trying to stop it. But you know how it goes: new language features are
always heavily abused until people shoot themselves in the foot enough
times and figure out what the "good" versus the "bad" abuses are :)

Miro Knejp

unread,
Feb 18, 2016, 1:08:11 PM2/18/16
to std-pr...@isocpp.org
Am 18.02.2016 um 18:06 schrieb Vicente J. Botet Escriba:
Le 18/02/2016 16:39, Miro Knejp a écrit :


Am 18.02.2016 um 10:40 schrieb Vicente J. Botet Escriba:
Le 17/02/2016 22:30, Miro Knejp a écrit :
I somehow can't shed the feeling that what we really need here is support for multiple return values.

I don't know if mandatory for the subject on this thread, but yes, I believe we need MRV, but I don't know exactly what this would mean yet.
Just imagine for a moment that the following is valid:
int, float, double foo();
void bar(int, float, double);
bar(foo());
auto {a, b, c} = foo(); // borrowed from P0144R1

Could you show how would you define foo() to return MRV?
    return 0, 1.0, 2.0;

or

    return 0 1.0 2.0;

Or?
There are examples further down in my post. Besides any syntax presented here is just a strawman so don't get lost in the details too much.

As you describe below, these details are important.

IIUC, the single things you can do with a MRV is to use it in a call expression and in this case each one of the values is associated to an argument, and
structure binding. However structure binding is associated to a tuple-like interface. Does it means that a MRV thing would have a tuple-like access interface (get<I>(mrv))?
No. If you want to return multiple values in something with a tupe-like interface, well then use tuple and P0144 binding. There's also a proposal in SG14 to synthesize get<>() for public fields, making any old struct compatible with P0144.
Could you give me a reference, because I'm the author of P0197 - Default tuple-like access and I'm really concerned ?
No need to be concerned, it is yours. I just didn't make the connection when writing my reply and confused it with something else.

It is important to keep the multiple return values separate in a special entity to allow for the compiler magic that unpacks it effortlessly into a list of parameters.
If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for some a1,a2,a3

it should also be expanded in the context of
auto {a, b, c} = foo();

and so we will need that

auto {a, b, c} = a1, a2, a3;
No, since "a1, a2, a3" is a comma-expression and the binding only applies to the result of the expression "a3". If P0144 doesn't consider this special case then it will become impossible to add it later.
P0144 can not take in account the MRV you are describing here ;-)
Of course it can't because MRVs don't exist yet and it doesn't cover parameter packs, which expand either to a comma-expression or a list of values, depending on the context. But since parameter packs are a distinct entity an excepation can be made for them in this context to not expand into a comma-expression.
That's why it's called strawman syntax. It's only meant for demonstration. You could also call it pseudo code if you like. Criticize the idea first, syntax last.



I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.
Well, as there is not yet a proposal for MRV we need to consider yours here, to see if is something that could be acceptable. What I mean is that we can not compare two solutions to a specific problem until we have concrete solutions and we can see the advantages and liabilities for both solutions. Maybe you prefer to start a new thread.
I haven't given the standard wording any thought yet so whether MRVs are their own thing or parameter packs or whatever is entirely open for debate (in a different thread if there is enough interest).
I'm not talking yet of wording, but of how to use the feature.
I provided examples of how to use it. Did I miss an important use case?

Matthew Woehlke

unread,
Feb 18, 2016, 1:14:30 PM2/18/16
to std-pr...@isocpp.org
On 2016-02-18 12:46, Miro Knejp wrote:
> Am 18.02.2016 um 17:18 schrieb Matthew Woehlke:
>> So... basically, an MRV *is* a parameter pack? In that case, I don't see
>> much difference between "true MRV's" and U2PP.
>
> Call it whatever you want, I don't care. At this stage I'm interested in
> the functionality not bikeshedding of syntax or classification.

That's not a bikeshedding question. A parameter pack is an existing
entity with known functionality. If TMRV's are also parameter packs (and
my impression is that we generally agree the should be), that gives
specific, detailed information about how they function and what they can do.

>> On 2016-02-17 18:38, Miro Knejp wrote:
>>> Without MRVs you cannot hide "[1:]tpl1..." effortlessly behind a named
>>> function.
>>
>> You can't?
>>
>> auto tail(auto tuple) { return make_tuple([1:]tuple...); }
>
> Yes, that is how I would implement `tail()` even with MRVs because
> `tail()` has the signature `(a_0, a_1, ..., a_n) -> (a_1, ..., a_n)`. A
> better name for the example would have been `unpack_tail(tpl)` or
> similar, my bad. Basically it needs the signature `(a_0, a_1, ..., a_n)
> -> a_1, ..., a_n`.
>
> But yet, those two are not equivalent. I cannot substitute the
> expression `[1:]tpl...` with the expression `unpack_tail(tpl)` using
> only the tools given in this thread and get the same semantics. Or am I
> wrong? That's what I mean with you can't `hide` it. A full substitution
> is `[:]unpack_tail(tpl)...` but that is a leaky abstraction because in
> order to get the same semantics you have to apply additional operators.

Well... yes, except a more accurate comparison would be:

unpack_tail(tpl)
// vs.
[:]tail(tpl)

...because the non-MRV version isn't unpacked. (If you actually *want* a
tuple, it's also more efficient, but that's bordering on a strawman
argument...)

It's not exactly a leaky abstraction; more of an inexact equivalence.
That is, you can't exactly implement `unpack_tail` with U2PP. What you
*can* implement is something that returns a tuple that can be unpacked
to obtain the same parameter pack. Is that a problem? Well... maybe. I'm
not sure.

> `tail()` is a ubiquitous operation in basically every language that has
> some form of builtin list or tuple type.

It's also the accessor of the last item in a queue or linked list. :-)

>> I'm not thrilled that I can't tell there that `something(x)` is a
>> parameter pack. In that sense, I prefer the clarity of
>> `f([:]something(x)...)`. (Note that this is also how e.g. Python works.
>> Despite often being used as the poster child for MRV's, Python actually
>> has no such thing... just lots of syntax sugar a la P0144 and U2PP that
>> makes it seem as if it does.)
>
> I call that paranoia, as is the case with every new language feature.
> Soon we won't be able to tell whether `void foo(X x)` is the definition
> of a function template or not. Are you also not going to avoid that?

Well... yeah, I'm not crazy about that either :-).

>> Oh! Here's a fun point... if we *did* have true MRV's, how long until
>> people start writing unary operator* as a synonym for unpack? Would that
>> be a good thing?
>
> People define their own operators for DSLs or convenience all the time.
> If it gets established as common practice then there's no point in
> trying to stop it. But you know how it goes: new language features are
> always heavily abused until people shoot themselves in the foot enough
> times and figure out what the "good" versus the "bad" abuses are :)

I should clarify that the above was *not* meant as a rhetorical question
:-). One possibly "good" think about TMRV's as you propose, including
omission of `...`, is that I can write:

void dot(double, double, double);
vector_3d p = ...;
dot(*p);

...just as in Python. I suspect some people will love this while others
will be passionately opposed to it...

--
Matthew

Matthew Woehlke

unread,
Feb 18, 2016, 1:20:43 PM2/18/16
to std-pr...@isocpp.org
On 2016-02-18 13:10, Miro Knejp wrote:
> Am 18.02.2016 um 18:06 schrieb Vicente J. Botet Escriba:
>> Le 18/02/2016 16:39, Miro Knejp a écrit :
>>> If you want to return multiple values in something with a
>>> tupe-like interface, well then use tuple and P0144 binding. There's
>>> also a proposal in SG14 to synthesize get<>() for public fields,
>>> making any old struct compatible with P0144.
>>
>> Could you give me a reference, because I'm the author of P0197 -
>> Default tuple-like access and I'm really concerned ?
>
> No need to be concerned, it is yours. I just didn't make the connection
> when writing my reply and confused it with something else.

Okay... thanks. (Whew, the presenter of P0197¹ was also worried there
for a moment :-).)

(¹ That'd be me, if it wasn't obvious...)

>> P0144 can not take in account the MRV you are describing here ;-)
>
> Of course it can't because MRVs don't exist yet and it doesn't cover
> parameter packs, which expand either to a comma-expression or a list of
> values, depending on the context. But since parameter packs are a
> distinct entity an excepation can be made for them in this context to
> not expand into a comma-expression.

Right. As mentioned in my other post, I don't think there is a "problem"
here. (We'd certainly want to broaden assignment unpacking to handle
parameter packs, but that shouldn't be troublesome, just a change to be
made.)

--
Matthew

Reply all
Reply to author
Forward
0 new messages