Distinction between Argument Packs and Tuples

136 views
Skip to first unread message

strass...@gmail.com

unread,
Oct 10, 2015, 3:30:56 PM10/10/15
to ISO C++ Standard - Discussion
In C++ there is a clear distinction between variadic template argument packs and tuples. They have a vastly different syntax. However semantically they seem to do a  very similar thing: Store a vector with heterogeneous types whose length and element types need to be known at compile time. Why distinguish between argument packs and tuples?

I can easily turn an argument pack args into a tuple by writing make_tuple(args...). The other way around is significantly more verbose but possible. How to do it is illustrated in std::experimental::apply. The fact that I can do a full roundtrip without loosing any information shows that the two are actually semantically the same thing, just with a vastly different syntax. The fact that std::experimental::apply exists also shows that there is a demand to do the conversion in both directions.

In other languages, such as for example Python, this distinction is not made. For example in Python I can write:

def foo(first_argument, *other_arguments_as_tuple):
 
print("the first argument is", first_argument)
 
print("the remaining arguments in tuple form are", other_arguments_as_tuple)
 
print("the remaining arguments in unpacked form are", *other_arguments_as_tuple)

Not doing this distinction completely eliminates the need for the conversions between tuples and packs and thus the need for std::experimental::apply and would therefore, in my eyes, vastly simplify things without loosing anything.

Why not remove argument packs and implement std::tuple using compiler magic and extend it to fulfill the few roles that argument packs are needed for? To illustrate the question please look at the following code, where no distinction is made:

template<class ...Args>
void foo(Args... args){
   
// Args is an ordinary type of the form std::tuple<...some deduced types...>
   
// args is an instance of Args

   bar
(args...); // ... is a special operator of std::tuple.

   
// As args is just a tuple we can do the following with ease

   har
(std::get<5>(args), std::get<3>(args), std::get<2>(args));
}



What was the rational to have different entities for argument packs and tuples?

Best Regards
Ben Strasser

Thiago Macieira

unread,
Oct 10, 2015, 3:58:24 PM10/10/15
to std-dis...@isocpp.org
On Saturday 10 October 2015 12:30:56 strass...@gmail.com wrote:
> What was the rational to have different entities for argument packs and
> tuples?

A std::tuple is a library class that uses variadic templates and argument
packs. There may be other libraries with other classes doing similar things.
std::tuple is not special.

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

Johannes Schaub

unread,
Oct 11, 2015, 7:53:15 AM10/11/15
to std-dis...@isocpp.org
I would say because arguments in C++ are N differen values, while a
tuple is a single compound value. IMO there are two mental models of a
tuple. One in which a tuple is not a compound value, but simply means
a sequence of N values accessed by a single name. Such a model implies
tuples cannot nest. The other model is that a tuple is a compound
value, and can nest.

I would rather propose such unpacking and automatic-packing for
initializer lists (and for the parameter it would have a special
syntax, such as "std::tuple<Args...> args" which could be
initializable by a "{ ... }" argument and deduce Args (recursively
aswell, perhaps, so that "{ .. { ... } ...}" would deduce to a nested
tuple).

Matthew Woehlke

unread,
Oct 12, 2015, 10:37:19 AM10/12/15
to std-dis...@isocpp.org
On 2015-10-10 15:30, strass...@gmail.com wrote:
> In C++ there is a clear distinction between variadic template argument
> packs and tuples. They have a vastly different syntax. However semantically
> they seem to do a very similar thing: Store a vector with heterogeneous
> types whose length and element types need to be known at compile time. Why
> distinguish between argument packs and tuples?
>
> I can easily turn an argument pack args into a tuple by writing
> make_tuple(args...). The other way around is significantly more verbose but
> possible. How to do it is illustrated in std::experimental::apply. The fact
> that I can do a full roundtrip without loosing any information shows that
> the two are actually semantically the same thing, just with a vastly
> different syntax. The fact that std::experimental::apply exists also shows
> that there is a demand to do the conversion in both directions.

Note that there is a problem with std::x::apply; it does not provide an
equivalent for this Python code:

t = make_some_tuple
foo(arg1, arg2, *t, argn)

I've mentioned before that I'd like to see a language level equivalent
of std::x::apply, that would allow writing things like:

auto t1 = ...; // something "unpackable", not necessarily std::tuple
auto t2 = ...; // likewise

foo(a, b, [*]t1, c, [*]t2, d, e);

That is, I can mix unpacking and regular arguments, unpack multiple
entities into a single pack / argument list, etc.

> Why not remove argument packs and implement std::tuple using compiler magic
> and extend it to fulfill the few roles that argument packs are needed for?

Because a template argument pack is not a real type; it is a concept
that exists only prior to code generation, at which time it is expanded
inline. If you try to make them a tuple, you are forcing an object to be
constructed from the arguments, where currently we don't do that.

> // As args is just a tuple we can do the following with ease
> har(std::get<5>(args), std::get<3>(args), std::get<2>(args));

This should also work with a type pack. Maybe it should work with
template argument packs also... (Note however that you could already
write your own function to do the same thing.)

--
Matthew

strass...@gmail.com

unread,
Oct 14, 2015, 3:00:38 PM10/14/15
to ISO C++ Standard - Discussion

@Johannes: Indeed, initializer_lists are a third form of tuples.


Matthew Woehlke wrote:
Because a template argument pack is not a real type; it is a concept
that exists only prior to code generation, at which time it is expanded
inline. If you try to make them a tuple, you are forcing an object to be
constructed from the arguments, where currently we don't do that.

I do not see why unpacking tuple types/argument packs before code generation is something useful. I understand that this is the way things happen to be. What I do not see is the advantage of having this. What I however do see are the advantages of having just one tuple in the language: Less gluing code needed, IMO simpler semantics, and stuff like unpacking arbitrary tuples just works.

What is the reason that you want to have unpackable types beyond tuples and the semi-type-like argument packs?



Can someone come up with code that is legal today but that would break or misbehave if one did the changed proposed in my first email? The summary of the changes would be:

template<class ... Args> // the ... would become superfluous here
void foo(Args...args); // Args is inferred as std::tuple type, args as instance of this type

template<class ... Args> // the ... would become superfluous here
class Foo; // Args is inferred as std::tuple type, there is no instance
 
std::tuple<int, std::string>a;
foo
(a...); // compiler replaces this with foo(get<0>(a), get<1>(a));
foo
(bar(a)...); // compiler replaces this with foo(bar(get<0>(a)), bar(get<1>(a)));

typedef std::tuple<int, std::string> K;
std
::pair<K...> x; // compiler replaces this with std::pair<int, std::string>

Nicol Bolas

unread,
Oct 14, 2015, 8:06:51 PM10/14/15
to ISO C++ Standard - Discussion, strass...@gmail.com
On Wednesday, October 14, 2015 at 3:00:38 PM UTC-4, strass...@gmail.com wrote:
@Johannes: Indeed, initializer_lists are a third form of tuples.

Matthew Woehlke wrote:
Because a template argument pack is not a real type; it is a concept
that exists only prior to code generation, at which time it is expanded
inline. If you try to make them a tuple, you are forcing an object to be
constructed from the arguments, where currently we don't do that.

I do not see why unpacking tuple types/argument packs before code generation is something useful. I understand that this is the way things happen to be. What I do not see is the advantage of having this. What I however do see are the advantages of having just one tuple in the language: Less gluing code needed, IMO simpler semantics, and stuff like unpacking arbitrary tuples just works.

C++ only has one tuple type.
 
What is the reason that you want to have unpackable types beyond tuples and the semi-type-like argument packs?

Argument packs are not "semi-type-like". They are very much unlike types in C++. Indeed, they have nothing at all in common with types.

You can't pass a parameter pack to a function the way you can with a value. You can't declare a variable of a type pack. You can't do innumerable things with them. Indeed, there are only two things you can do with a pack: expand it, and get the size of it.

They're nothing at all like types or values. They may conceptually contain such, but they do so in a way that is very different from tuples.
 
Can someone come up with code that is legal today but that would break or misbehave if one did the changed proposed in my first email?

... who cares if someone can or cannot? What it most certainly would do is turn fast, compiler-generated code into slower, library-based code. And when I talk about performance, I'm talking about compilation speed.

Template parameter packs and template argument packs being first-class constructs allows the compiler to do a lot of useful things. It allows the compiler to skip having to write `get<>` and such, relative to tuples. It can convert the code directly into what it should be. The compiler doesn't have to wait on the optimizer to inline a bunch of code to make it fast. The compiler doesn't have to recursively instantiate half-a-dozen templates. It just does its thing and unpacks the pack.

That saves compilation time. Lots of time.

I'm not saying that it wouldn't be useful to apply `...` expansion to tuples (or even better, allow it to be applied to arbitrary types, given an appropriate infrastructure). But we shouldn't transform a fast language construct into a slow library abstraction. Allow expansions to work on tuples if you like, but don't force template parameter packs to actually become tuples. That's pointless.

Also, what about non-type template packs? Are those supposed to turn into `tuple<int, int, int, ...>` or whatever?

One final point on this matter. The fact that a proposal doesn't break existing code is not a justification for adopting it. It's merely shooting down a reason not to adopt it.

Richard Smith

unread,
Oct 14, 2015, 9:04:52 PM10/14/15
to std-dis...@isocpp.org
On Wed, Oct 14, 2015 at 12:00 PM, <strass...@gmail.com> wrote:

@Johannes: Indeed, initializer_lists are a third form of tuples.

Matthew Woehlke wrote:
Because a template argument pack is not a real type; it is a concept
that exists only prior to code generation, at which time it is expanded
inline. If you try to make them a tuple, you are forcing an object to be
constructed from the arguments, where currently we don't do that.

I do not see why unpacking tuple types/argument packs before code generation is something useful. I understand that this is the way things happen to be. What I do not see is the advantage of having this. What I however do see are the advantages of having just one tuple in the language: Less gluing code needed, IMO simpler semantics, and stuff like unpacking arbitrary tuples just works.

What is the reason that you want to have unpackable types beyond tuples and the semi-type-like argument packs?



Can someone come up with code that is legal today but that would break or misbehave if one did the changed proposed in my first email?

template<typename ...T> void f(T ...t) {
  std::tuple<int, int> x;
  g(h(x, t)...);
}
 
The summary of the changes would be:

template<class ... Args> // the ... would become superfluous here
void foo(Args...args); // Args is inferred as std::tuple type, args as instance of this type

template<class ... Args> // the ... would become superfluous here
class Foo; // Args is inferred as std::tuple type, there is no instance
 
std::tuple<int, std::string>a;
foo
(a...); // compiler replaces this with foo(get<0>(a), get<1>(a));
foo
(bar(a)...); // compiler replaces this with foo(bar(get<0>(a)), bar(get<1>(a)));

typedef std::tuple<int, std::string> K;
std
::pair<K...> x; // compiler replaces this with std::pair<int, std::string>

--

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

strass...@gmail.com

unread,
Oct 15, 2015, 3:32:59 AM10/15/15
to ISO C++ Standard - Discussion
@Richard Smith
Indeed that breaks... :/ thank you for pointing that out.



What it most certainly would do is turn fast, compiler-generated code into slower, library-based code.

Part of the proposal was to implement std::tuple inside of the compiler. No library-base code at all. There is no reason why this should compile slower. If there is any difference at all, then this should make code using tuples faster as tuple is no longer library-based.

Vicente J. Botet Escriba

unread,
Oct 16, 2015, 4:52:35 PM10/16/15
to std-dis...@isocpp.org
Le 12/10/15 16:37, Matthew Woehlke a écrit :
> On 2015-10-10 15:30, strass...@gmail.com wrote:
>> In C++ there is a clear distinction between variadic template argument
>> packs and tuples. They have a vastly different syntax. However semantically
>> they seem to do a very similar thing: Store a vector with heterogeneous
>> types whose length and element types need to be known at compile time. Why
>> distinguish between argument packs and tuples?
>>
>> I can easily turn an argument pack args into a tuple by writing
>> make_tuple(args...). The other way around is significantly more verbose but
>> possible. How to do it is illustrated in std::experimental::apply. The fact
>> that I can do a full roundtrip without loosing any information shows that
>> the two are actually semantically the same thing, just with a vastly
>> different syntax. The fact that std::experimental::apply exists also shows
>> that there is a demand to do the conversion in both directions.
> Note that there is a problem with std::x::apply; it does not provide an
> equivalent for this Python code:
>
> t = make_some_tuple
> foo(arg1, arg2, *t, argn)
You could have

invoke(foo, arg1, arg2, args(t), argn);

where args(t) wraps a tuple as ref wraps a reference so that

invoke(foo, arg1, arg2, args(t), argn);

would unpack the wrapped tuple t and be equivalent to

invoke(foo, arg1, arg2, get<0>(t), ..., get<k-1>(t), argn); // k is
the tuple_size of t


> I've mentioned before that I'd like to see a language level equivalent
> of std::x::apply, that would allow writing things like:
>
> auto t1 = ...; // something "unpackable", not necessarily std::tuple
> auto t2 = ...; // likewise
>
> foo(a, b, [*]t1, c, [*]t2, d, e);

invole(foo, a, b, args(t1), c, args(t2), d, e);



Vicente

Matthew Woehlke

unread,
Oct 16, 2015, 5:10:30 PM10/16/15
to std-dis...@isocpp.org
On 2015-10-16 16:52, Vicente J. Botet Escriba wrote:
> Le 12/10/15 16:37, Matthew Woehlke a écrit :
>> Note that there is a problem with std::x::apply; it does not provide an
>> equivalent for this Python code:
>>
>> t = make_some_tuple
>> foo(arg1, arg2, *t, argn)
>
> You could have
>
> invoke(foo, arg1, arg2, args(t), argn);
>
> where args(t) wraps a tuple as ref wraps a reference so that
>
> invoke(foo, arg1, arg2, args(t), argn);
>
> would unpack the wrapped tuple t and be equivalent to
>
> invoke(foo, arg1, arg2, get<0>(t), ..., get<k-1>(t), argn); // k is
> the tuple_size of t

I'm confused; how would you implement this 'args' *in current C++*? (By
"current", I'll give you c++1z in its form as of today.)

--
Matthew

T. C.

unread,
Oct 16, 2015, 6:33:51 PM10/16/15
to ISO C++ Standard - Discussion, mwoehlk...@gmail.com
One way (perfect forwarding omitted for brevity):

`args()` return a special type (e.g. "tuple_wrapper"). 

Write a fixup_args() that wraps ordinary arguments into a (single-element) std::tuple
with forward_as_tuple, and unwraps tuple_wrapper into a tuple. 

Then `apply(foo, tuple_cat(fixup_args(arguments)...));`

Vicente J. Botet Escriba

unread,
Oct 17, 2015, 7:31:52 PM10/17/15
to std-dis...@isocpp.org
Le 17/10/15 00:33, T. C. a écrit :

On Friday, October 16, 2015 at 5:10:30 PM UTC-4, Matthew Woehlke wrote:
On 2015-10-16 16:52, Vicente J. Botet Escriba wrote: 
Le 12/10/15 16:37, Matthew Woehlke a écrit : 
Note that there is a problem with std::x::apply; it does not provide an 
equivalent for this Python code: 

   t = make_some_tuple 
   foo(arg1, arg2, *t, argn) 
You could have 

    invoke(foo, arg1, arg2, args(t), argn); 

where args(t) wraps a tuple as ref wraps a reference so that 

    invoke(foo, arg1, arg2, args(t), argn); 

would unpack the wrapped tuple t and be equivalent to 

    invoke(foo, arg1, arg2, get<0>(t), ..., get<k-1>(t), argn); // k is 
the tuple_size of t 
I'm confused; how would you implement this 'args' *in current C++*? (By 
"current", I'll give you c++1z in its form as of today.) 

-- 
Matthew 


One way (perfect forwarding omitted for brevity):

`args()` return a special type (e.g. "tuple_wrapper"). 
Right.


Write a fixup_args() that wraps ordinary arguments into a (single-element) 
std::tuple
with forward_as_tuple, and unwraps tuple_wrapper into a tuple. 

Then `apply(foo, tuple_cat(fixup_args(arguments)...));`

Thanks Tony for the description of a possible implementation.

My idea is that invoke can take care of the tuple_wrapper directly.

Vicente

Matthew Woehlke

unread,
Oct 19, 2015, 10:57:17 AM10/19/15
to std-dis...@isocpp.org
On 2015-10-16 18:33, T. C. wrote:
> On Friday, October 16, 2015 at 5:10:30 PM UTC-4, Matthew Woehlke wrote:
>> I'm confused; how would you implement this 'args' *in current C++*? (By
>> "current", I'll give you c++1z in its form as of today.)
>
> One way (perfect forwarding omitted for brevity):
>
> `args()` return a special type (e.g. "tuple_wrapper").
>
> Write a fixup_args() that wraps ordinary arguments into a (single-element)
> std::tuple
> with forward_as_tuple, and unwraps tuple_wrapper into a tuple.
>
> Then `apply(foo, tuple_cat(fixup_args(arguments)...));`

Eep. Okay, yeah, agree that looks like it would work. Sure is ugly,
though, compared to a '[*]' (or '...', or whatever) operator to ask the
compiler to do the work more directly.

--
Matthew

Reply all
Reply to author
Forward
0 new messages