Variadic Templates in C++0x need some additional features to come closer to fulfilling their promise

146 views
Skip to first unread message

Faisal Vali

unread,
Jan 9, 2009, 2:35:09 AM1/9/09
to
I am truly grateful to Doug Gregor (and team) for his/their inspiring
work on designing and implementing Variadic templates, so I hope this
is not viewed as a negative post, since even in their current form,
variadics do reduce a lot of repetitive code, and I know, if it wasn't
for him and his team, we wouldn't have variadics in C++0x.

But I still feel we're somewhat removed from harnessing their true
potential. If you look at the implementation for tuple and bind in
n2080 (off the C++ standards website) the implementation seems
unnecessarily complicated for such conceptually simple ideas. One
still gets the sense that they are knitting hacks together - and the
solution seems to require a fair amount of boiler plate.

After a discussion on comp.lang.c++.moderated, I was convinced that
Variadics need the following additional features, to allow them to
fulfill their promise - and i have put together some standardese (for
a couple of the features) to provide a starting point for the
committee, should they ever consider these features seriously at some
point in the future.

1) Member Declaration Packs:
template<class ... V> struct store { V...members; };
Standardese: http://c-plus-plus-0x.wikidot.com/c-0x-variadic-templates:member-declaration-packs-proposal


2) Pack expansions of arbitrary expressions:
template<class ... V> bool v_and(V...v) { return true && v ...; }
Standardese: http://c-plus-plus-0x.wikidot.com/variadic-templates:pack-expansion-of-arbitrary-expressions


3) A guaranteed non-recursive way to access elements of parameter
packs
template<int N, class ... V> struct get_type
{
typedef V@N type; // or implementation_defined<N,V...>::type -
guaranteed linear
};
template<int N, class ... V> get_type<N,V...>::type get(V...v) {
return ::implementation_defined<N>(v...);
}


Just as an example, if parameter packs are allowed, compare how much
simpler this implementation of tuple is than that described in n2080
(which hacks (elegantly nevertheless) together a recursive list-like
implementation).


template<class ... Types> struct tuple {

tuple(Types&& ... types) : types(types) ... { }
tuple() : types() ... { }

template <class ... Types2>
tuple(const tuple<Types2...>& other) : types(other.types) ... { }

template <class ... Types2>
tuple& operator=(const tuple<Types2...>& rhs)
{
v_eval( (types = rhs.types, 0) ... );
return this;
}
template<class ... Types2> bool operator==(const tuple<Types2...>&
rhs)
{
return v_and( (types == rhs.types)...);
}

template<class ... Types2> bool operator!=(const tuple<Types2...>&
rhs)
{
return !operator==(rhs);
}

template<class ... Types2> bool operator<(const tuple<Types2...>&
rhs)
{
return v_and( (types < rhs.types) ...);
}

Types ... types;

};

// These are universal helpers - you can actually use this idiom in
conceptg++ to expand arbitrary expressions
// as long as the expressions are separated by commas
template<class ... EvalT> void v_eval(EvalT ... ) { }

bool v_and() { return true; }
template<class First, class ... Rest> bool v_and(First&& f, Rest&& ...
rest)
{
return f && v_and(rest...);
}

The above code becomes even simpler if pack expansions of arbitrary
expressions are allowed (conceptg++ already allows the following -
which means implementation precedent exists somewhat - just need to
get the logic for the separator right)

template<class ... Args1> void v_print(Args1 ... args1)
{
int i = 0;
v_eval( ((std::cout << "[" << i++ << ":" << typeid(Args1).name() <<
"] = " << args1 << std::endl),0)... );
}

template<class ... Args1> void v_add5(Args1 ... args1)
{
v_print(4, (args1 + 5 + args1)...);
}

Does anyone else feel strongly about these features? If enough of us
do, my hope is that we can push this through C++0x, but realistically,
I would expect this to be relegated to a TR.

regards,
Faisal Vali
Radiation Oncology
Loyola

--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

Hyman Rosen

unread,
Jan 9, 2009, 4:59:04 PM1/9/09
to
Faisal Vali wrote:
>
> 1) Member Declaration Packs:
> template<class ... V> struct store { V...members; };
> Standardese: http://c-plus-plus-0x.wikidot.com/c-0x-variadic-templates:member-declaration-packs-proposal

Pardon me if this has already been explained, but why does
your proposal explicitly disallow static parameter packs?
What's wrong with the following?
template <class ...V> struct X { static V...members; };
template <class ...V> V X::...members;

Douglas Gregor

unread,
Jan 11, 2009, 4:05:38 AM1/11/09
to
On Jan 8, 11:35 pm, Faisal Vali <fais...@gmail.com> wrote:
> After a discussion on comp.lang.c++.moderated, I was convinced that
> Variadics need the following additional features, to allow them to
> fulfill their promise - and i have put together some standardese (for
> a couple of the features) to provide a starting point for the
> committee, should they ever consider these features seriously at some
> point in the future.
>
> 1) Member Declaration Packs:
> template<class ... V> struct store { V...members; };
> Standardese:http://c-plus-plus-0x.wikidot.com/c-0x-variadic-templates:member-decl...

Permitting members to be parameter packs introduces ambiguities that
need to be solved. Some example code:

template<typename … T> struct S1 {
T ...members;
};

template<typename... T> struct S2 {
int members;
};

template<typename... Ts> void f(Ts...);

The problem, now, is that if we have an expression like "s.members",
where "s" is a type-dependent expression, we don't know whether
"s.members" refers to a parameter pack or not. Here's an example where
we just don't know which things are parameter packs:

template<typename T1, typename T2> struct WhichParamPacks {
void g(T1 t1, T2 t2) {
f(t1.members + t2.members...); // which of t1.members or
t2.members is a parameter pack?
}
};

Using a more complicated expression in the call to f() would make
things even more interesting, because it isn't clear what the
structure of the expression is with respect to the parameter packs.
The trouble gets compounded if you have nested pack expansions,
because it isn't clear how certain expressions get captured within
"...", e.g.,

void g2(T1 t1, T2 t2) {
void f(f(t1.members...) + t2.members...);
}

The ambiguity manifests in other problems, too. Here, we're using
decltype() to move the problem into the type system. Given this:

template<typename T> struct ParamPackOrEllipsis {
void h(decltype((*((T*)0)).members...);
};

ParamPackOrEllipsis<S1<something>>::h would have a parameter pack,
while ParamPackOrEllipsis<S2<something>>::h has an ellipsis. Very
strange!

For this feature to work, I think you'll need to introduce some kind
of syntax that says "the entity I'm referring to is a parameter pack".
We could take our idea from "typename" and "template" and come up with
some ugly syntax like:

void g(T1 t1, T2 t2) {
f(t1....members + t2.members...); // t1.members is a parameter
pack, t2.members is not
}


> 2) Pack expansions of arbitrary expressions:
> template<class ... V> bool v_and(V...v) { return true && v ...; }

> Standardese:http://c-plus-plus-0x.wikidot.com/variadic-templates:pack-expansion-o...

This seems technically feasible.

> 3) A guaranteed non-recursive way to access elements of parameter
> packs
> template<int N, class ... V> struct get_type
> {
> typedef V@N type; // or implementation_defined<N,V...>::type -
> guaranteed linear
> };
> template<int N, class ... V> get_type<N,V...>::type get(V...v) {
> return ::implementation_defined<N>(v...);
> }

This is probably the most-requested feature for variadic templates,
and it never it made it because we never found a good, unambiguous
syntax.

> Just as an example, if parameter packs are allowed, compare how much
> simpler this implementation of tuple is than that described in n2080
> (which hacks (elegantly nevertheless) together a recursive list-like
> implementation).

[snip example]

We've actually found a way to eliminate the need for the recursive
list-like implementation of tuple. It makes use of derivation from a
pack expansion.

> template<class ... Args1> void v_print(Args1 ... args1)
> {
> int i = 0;
> v_eval( ((std::cout << "[" << i++ << ":" << typeid(Args1).name() <<
> "] = " << args1 << std::endl),0)... );

The only issue with this idiom is that the order of evaluation of the
arguments to v_eval is not specified, so you won't get consistent
output on different platforms.

- Doug

Larry Evans

unread,
Jan 11, 2009, 11:22:03 AM1/11/09
to
On 01/11/09 03:05, Douglas Gregor wrote:
> On Jan 8, 11:35 pm, Faisal Vali <fais...@gmail.com> wrote:
[snip]

>> Just as an example, if parameter packs are allowed, compare how much
>> simpler this implementation of tuple is than that described in n2080
>> (which hacks (elegantly nevertheless) together a recursive list-like
>> implementation).
> [snip example]
>
> We've actually found a way to eliminate the need for the recursive
> list-like implementation of tuple. It makes use of derivation from a
> pack expansion.
[snip]
Doug, could you post a reference or explain how this nonrecursive
tuple implementation was done?

TIA.

-Larry

Douglas Gregor

unread,
Jan 12, 2009, 8:07:31 PM1/12/09
to
On Jan 11, 8:22 am, Larry Evans <cppljev...@suddenlink.net> wrote:
> On 01/11/09 03:05, Douglas Gregor wrote:> On Jan 8, 11:35 pm, Faisal Vali
<fais...@gmail.com> wrote:
> [snip]
> >> Just as an example, if parameter packs are allowed, compare how much
> >> simpler this implementation of tuple is than that described in n2080
> >> (which hacks (elegantly nevertheless) together a recursive list-like
> >> implementation).
> > [snip example]
>
> > We've actually found a way to eliminate the need for the recursive
> > list-like implementation of tuple. It makes use of derivation from a
> > pack expansion.
>
> [snip]
> Doug, could you post a reference or explain how this nonrecursive
> tuple implementation was done?

Start with the make_indices function from the Function Object Binders
section of n2080. Then, we create a class template to store the tuple
elements indexed by their position:

template<int I, typename T>
struct _Tuple_element {
T value;
};

Now, class template tuple is going to inherit from a special
_Tuple_impl:

template<typename... Elements>
class tuple : public _Tuple_impl<typename make_indices<sizeof...
(Elements)>::type, _Elements...> { /* skip for now */ };

where _Tuple_impl does the fun stuff:

template<typename IntTuple, typename... Elements>
struct _Tuple_impl;

template<int... Indices, typename... Elements>
struct _Tuple_impl<int_tuple<Indices...>, Elements...>
: public _Tuple_element<Indices, Elements>... { /* and so on */ };

The rest can be an exercise for the reader :)

Note that this isn't 100% non-recursive, because the make_indices
metafunction is recursive. However, it's a tiny metafunction whose
results will likely be used in several different places in the
library.

- Doug

- Doug


--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use

mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>

Faisal Vali

unread,
Jan 13, 2009, 5:13:35 AM1/13/09
to
On Jan 11, 3:05 am, Douglas Gregor <doug.gre...@gmail.com> wrote:
> On Jan 8, 11:35 pm, Faisal Vali <fais...@gmail.com> wrote:
>

> > 1) Member Declaration Packs:
> > template<class ... V> struct store { V...members; };
> > Standardese:http://c-plus-plus-0x.wikidot.com/c-0x-variadic-templates:member-decl...
>
> Permitting members to be parameter packs introduces ambiguities that
> need to be solved. Some example code:
>

<snip>


> template<typename T1, typename T2> struct WhichParamPacks {
> void g(T1 t1, T2 t2) {
> f(t1.members + t2.members...); // which of t1.members or t2.members is a parameter pack?
> }
> };
>

> template<typename T> struct ParamPackOrEllipsis {
> void h(decltype((*((T*)0)).members...);
>
> };
>
> ParamPackOrEllipsis<S1<something>>::h would have a parameter pack,
> while ParamPackOrEllipsis<S2<something>>::h has an ellipsis. Very
> strange!
> For this feature to work, I think you'll need to introduce some kind
> of syntax that says "the entity I'm referring to is a parameter pack".
> We could take our idea from "typename" and "template" and come up with
> some ugly syntax like:

> void g(T1 t1, T2 t2) {

> f(t1....members + t2.members...); // t1.members is a pack, t2.members is not
> }

In addition to your suggestion of using 4 dots for accessing member
declaration packs, we could also consider the following (in type-
dependent expressions)
1) empty brackets
f((t1.members[] + t2.members)...) // empty brackets to the right of
a data-member access means it is a pack
f( t1.*t2.dm_ptrs[]...); // f(t1.*t2.dm_ptrs$1, t1.*t2.dm_ptrs
$2 ...)
f( t1.*dm_ptrs[] ) // ill-formed even if in a member function since
we can only use []
// next to member declaration packs during
'class member access'
// in a type dependent expression (not sure if
we need this limitation - might be easier without)

template<class T1, class T2> void foo(T1 t1_, T2 t2_)
{
static T1 t1 = t1_; static T2 t2 = t2_; // make statics so we can
refer to them in the local class
struct Local {
void bar(decltype(t1.members[] + t2.members) ... /*args*/
[]);
// expands as : void bar(decltype(t1.members$1 +
t2.members) /*args$1*/[], ...);

void bar2(decltype(t1.members[])...); // per 8.3.5/14 - has to
be parsed as a parameter pack
};

};

// struct S { char members[] = "abc"; }; // you can never refer to
this as s.members[] - so no ambiguity
// You can never have a _data_ member named operator so no
ambiguity with s.operator[]

f( new (
decltype(t1.members[]) //
return type of function ptr varies
(*[sizeof...(t1.members[])]) // ptr
to array [PACK_LENGTH] of function ptrs
(decltype(t2.members)) //
parameter type of function ptr stays same
)(nullptr)...);

v_eval( delete [] t1.members[] ...); // no ambiquity - assume all
elements of the pack are dynamic arrays
f( new (auto [10])(t1.members[]) ... ); // ok

2) t1.members<...> - busy - 5 keystrokes - not pretty - implies
members is a template - hints at variadics with ellipsis
- is it ambiguous? don't think so - but not sure
- don't need 'template' because nothing else can be '<..' but
t1.members < .5 poses a prob for 1 dot.
- could also consider t1.<>members - yuk

template<typename T> struct ParamPackOrEllipsis {

void h( decltype( (*((T*)0)).members<...>) ... ); // ok

};


3) ::. syntax? t1::.members - quite ugly, & not as intuitive as empty
brackets - but should be unambiguous (.:: might be ambiguous)


> Using a more complicated expression in the call to f() would make
> things even more interesting, because it isn't clear what the
> structure of the expression is with respect to the parameter packs.
> The trouble gets compounded if you have nested pack expansions,
> because it isn't clear how certain expressions get captured within
> "...", e.g.,
>

I think we should clarify how expressions get captured by "..." by
stating somewhere in the standard that when used in an expression,
expression-list, or initializer-list
"..." only captures unexpanded parameter-packs in a _pm-expression_
immediately to its left.

And that if the "..." follows a pattern that does not contain an
unexpanded parameter-pack the code is ill-formed.

For e.g.
f( sizeof...(pack)...) // ill-formed, since sizeof... expands its
packs
f( sizeof(pack)...) // ok - f(sizeof(pack$1),sizeof(pack$2), ...) -
f( o_pack->*dm_pack...) // ok - f( o_pack$1->*dm_pack$1,...)
f( pack1 ? pack2 : pack3 ...) // only captures pack3 - need parens
f( pack1 == pack2 ... ) // only captures pack2 - need parens

f ( i = pack1 ... = 0 ) // f(i = pack$1 = pack$2 = pack$3 ... =
0) -
// one argument supplied and all
packs are assigned 0 as side-effect

f ( (i = pack1 = 0)...) // f( i = pack$1 = 0, i = pack$2 = 0, ...)
- unspecified order of eval prob here too

template<class ... Ts> void f(decltype( getTsObj<Ts>() + 5 ) ...
args); // ok

Note - some of the above would need a rewording of the verbiage for
selecting the appropriate syntactic separator - but i think using the
idea that if the pattern is an operand of a binary operator, using its
syntactic category for expansion should work.

> > 2) Pack expansions of arbitrary expressions:
> > template<class ... V> bool v_and(V...v) { return true && v ...; }
> > Standardese:http://c-plus-plus-0x.wikidot.com/variadic-templates:pack-expansion-o...
>
> This seems technically feasible.

Does that mean that there is a chance this could make C++0x?

>
> > 3) A guaranteed non-recursive way to access elements of parameter
> > packs
> > template<int N, class ... V> struct get_type
> > {
> > typedef V@N type; // or implementation_defined<N,V...>::type -
> > guaranteed linear
> > };
> > template<int N, class ... V> get_type<N,V...>::type get(V...v) {
> > return ::implementation_defined<N>(v...);
> > }
>
> This is probably the most-requested feature for variadic templates,
> and it never it made it because we never found a good, unambiguous
> syntax.
>

Why can't we just have the following be specified in the standard
and have their implementation be some compiler magic that guarantees
constant access? (along the lines of what we specify with certain
compile time traits)

std::pack_get_type<N,Types...>::type // returns the expected type ...
std::pack_get<N>(args...) // returns the expected l-value ...


Also would we need special syntax to specify a certain member
declaration pack requirement in a certain
concept ?

Can we get away with solutions like these? (i.e. SFINAE type hacks)

namespace _private {
template<class ... Ts>
void dummy_fun(tuple<Ts...>& ) { }
}

auto concept HasMemberDeclarationPackNamedMembers<typename X> {

void dummy_fun(X)
{
_private::dummy_fun( tuple< decltype((*((X*)0)).members
[])...>& );;
}

};

Thanks.

regards,
Faisal Vali
Radiation Oncology
Loyola

--

Faisal Vali

unread,
Jan 13, 2009, 5:15:26 AM1/13/09
to
On Jan 9, 3:59 pm, Hyman Rosen <hyro...@mail.com> wrote:
> Faisal Vali wrote:
>
> > 1) Member Declaration Packs:
> > template<class ... V> struct store { V...members; };
> > Standardese:http://c-plus-plus-0x.wikidot.com/c-0x-variadic-templates:member-decl...

>
> Pardon me if this has already been explained, but why does
> your proposal explicitly disallow static parameter packs?
> What's wrong with the following?
> template <class ...V> struct X { static V...members; };
> template <class ...V> V X::...members;
>

No good reason asides from the fact that I thought it would be a lot
more work/changes and might make the proposal more unattractive (for C+
+0x). But i think it should definitely be considered seriously
(especially for TR1). Are you volunteering ? ;)

Douglas Gregor

unread,
Jan 13, 2009, 2:17:37 PM1/13/09
to
On Jan 13, 2:13 am, Faisal Vali <fais...@gmail.com> wrote:
> On Jan 11, 3:05 am, Douglas Gregor <doug.gre...@gmail.com> wrote:


> In addition to your suggestion of using 4 dots for accessing member
> declaration packs, we could also consider the following (in type-
> dependent expressions)
> 1) empty brackets

Seems feasible.

> 2) t1.members<...> - busy - 5 keystrokes - not pretty - implies
> members is a template - hints at variadics with ellipsis
> - is it ambiguous? don't think so - but not sure
> - don't need 'template' because nothing else can be '<..' but
> t1.members < .5 poses a prob for 1 dot.
> - could also consider t1.<>members - yuk

Ick :)

> 3) ::. syntax? t1::.members - quite ugly, & not as intuitive as empty
> brackets - but should be unambiguous (.:: might be ambiguous)

::. seems the wrong way to go, unless there's some kind of symmetry
between, e.g., "x....members" and "X::...types", where X is a
dependent name that will eventually refer to something like:

template<typename... Types>
struct Foo {
typedef Types ...types;
Types ...members;
};

> > Using a more complicated expression in the call to f() would make
> > things even more interesting, because it isn't clear what the
> > structure of the expression is with respect to the parameter packs.
> > The trouble gets compounded if you have nested pack expansions,
> > because it isn't clear how certain expressions get captured within
> > "...", e.g.,
>
> I think we should clarify how expressions get captured by "..." by
> stating somewhere in the standard that when used in an expression,
> expression-list, or initializer-list
> "..." only captures unexpanded parameter-packs in a _pm-expression_
> immediately to its left.

[temp.variadic]p4 is pretty clear about what's in the pattern and what
isn't in the pattern, and p5 covers the capturing behavior. How would
you like to clarify this?

> And that if the "..." follows a pattern that does not contain an
> unexpanded parameter-pack the code is ill-formed.

That's the third sentence of [temp.variadic]p5.

> For e.g.


> f( pack1 ? pack2 : pack3 ...) // only captures pack3 - need parens
> f( pack1 == pack2 ... ) // only captures pack2 - need parens

This would be an extremely unfortunate side effect if the change you
are proposing. Right now, the "..." captures the entire call argument,
and I think that's an important property.

> Note - some of the above would need a rewording of the verbiage for
> selecting the appropriate syntactic separator - but i think using the
> idea that if the pattern is an operand of a binary operator, using its
> syntactic category for expansion should work.

I'm a bit concerned about the change in behavior that you showed
above. Is there any other way?

> > > 2) Pack expansions of arbitrary expressions:
> > > template<class ... V> bool v_and(V...v) { return true && v ...; }
> > > Standardese:http://c-plus-plus-0x.wikidot.com/variadic-templates:pack-expansion-o...
>
> > This seems technically feasible.
>
> Does that mean that there is a chance this could make C++0x?

I doubt it. C++0x isn't supposed to get any new features. We can still
fix problems if they come up, but extensions that add new syntax? Not
likely. They certainly won't happen without an implementation.

> > > 3) A guaranteed non-recursive way to access elements of parameter
> > > packs
> > > template<int N, class ... V> struct get_type
> > > {
> > > typedef V@N type; // or implementation_defined<N,V...>::type -
> > > guaranteed linear
> > > };
> > > template<int N, class ... V> get_type<N,V...>::type get(V...v) {
> > > return ::implementation_defined<N>(v...);
> > > }
>
> > This is probably the most-requested feature for variadic templates,
> > and it never it made it because we never found a good, unambiguous
> > syntax.
>
> Why can't we just have the following be specified in the standard
> and have their implementation be some compiler magic that guarantees
> constant access? (along the lines of what we specify with certain
> compile time traits)
>
> std::pack_get_type<N,Types...>::type // returns the expected type ...
> std::pack_get<N>(args...) // returns the expected l-value ...

That's certainly possible. It's also small enough that it might be
able to still slip into C++0x. Of course, this works for function
parameter packs and template type parameter packs, but not template
template or template non-type parameter packs.

> Also would we need special syntax to specify a certain member
> declaration pack requirement in a certain
> concept ?

Yes, we would probably need special syntax.

> Can we get away with solutions like these? (i.e. SFINAE type hacks)
>
> namespace _private {
> template<class ... Ts>
> void dummy_fun(tuple<Ts...>& ) { }
>
> }
>
> auto concept HasMemberDeclarationPackNamedMembers<typename X> {
>
> void dummy_fun(X)
> {
> _private::dummy_fun( tuple< decltype((*((X*)0)).members
> [])...>& );;
> }
>
> };

No, metaprogramming tricks don't work within the bodies of concepts,
because they can't be type-checked at template definition time. In
this case, the name lookup of "members" is ill-formed in the body of
the associated function dummy_fun.

- Doug

Douglas Gregor

unread,
Jan 13, 2009, 2:18:06 PM1/13/09
to
On Jan 13, 2:15 am, Faisal Vali <fais...@gmail.com> wrote:
> On Jan 9, 3:59 pm, Hyman Rosen <hyro...@mail.com> wrote:
>
> > Faisal Vali wrote:
>
> > > 1) Member Declaration Packs:
> > > template<class ... V> struct store { V...members; };
> > > Standardese:http://c-plus-plus-0x.wikidot.com/c-0x-variadic-templates:member-decl...
>
> > Pardon me if this has already been explained, but why does
> > your proposal explicitly disallow static parameter packs?
> > What's wrong with the following?
> > template <class ...V> struct X { static V...members; };
> > template <class ...V> V X::...members;
>
> No good reason asides from the fact that I thought it would be a lot
> more work/changes and might make the proposal more unattractive (for C+
> +0x). But i think it should definitely be considered seriously
> (especially for TR1). Are you volunteering ? ;)

This is exactly the kind of design limitation that will certainly
result in a post to comp.std.c++ asking why this important feature
isn't supported :)

In all seriousness, once you've allowed one kind of member parameter
pack, it's not much harder to handle other kinds of parameter packs
(typedefs and static data members, at least) and it'll look like an
obvious omission. The syntactic troubles with having to deal with
dependent names that might be parameter packs are far, far more
difficult than the increment step from, e.g., non-static data member
packs to type member packs.

- Doug

Faisal Vali

unread,
Jan 14, 2009, 12:09:03 AM1/14/09
to
On Jan 13, 1:17 pm, Douglas Gregor <doug.gre...@gmail.com> wrote:
> On Jan 13, 2:13 am, Faisal Vali <fais...@gmail.com> wrote:
>
> > On Jan 11, 3:05 am, Douglas Gregor <doug.gre...@gmail.com> wrote:
> > In addition to your suggestion of using 4 dots for accessing member
> > declaration packs, we could also consider the following (in type-
> > dependent expressions)
> > 1) empty brackets
>
> Seems feasible.

Also, I am not too familiar with VLAs in C and whether their access
syntax allows s.members[]?
And we would have to ensure to prevent any ambiguity with lambda
introducers.

Which reminds me - is there any way to refer to function parameter
packs within lambdas?


>
> > 3) ::. syntax? t1::.members - quite ugly, & not as intuitive as empty
> > brackets - but should be unambiguous (.:: might be ambiguous)
>
> ::. seems the wrong way to go, unless there's some kind of symmetry
> between, e.g., "x....members" and "X::...types", where X is a
> dependent name that will eventually refer to something like:
>
> template<typename... Types>
> struct Foo {
> typedef Types ...types;
> Types ...members;
> };
>


Hmm, I can see the appeal of this 'dot'orrhea syntax ;)
Foo::types[] vs Foo::...types
foo.members[] vs foo....members
Foo::static_members[] vs Foo::...static_members

I could live with either.
The one advantage of the [] syntax is that you can use it uniformly to
refer to parameter packs
even without member access (assuming no ambiguity issues - nothing has
leaped out at me so far).
I have unfounded alarm bells going off about the ellipsis prefix in
regards to syntactical ambiguity
- although they might be prove to be red-herrings upon further
reflection.


template<class ... Ts> struct B
{
typedef Ts...types;
Ts ... datas;
B(Ts ... datas): datas[](datas)... { }
};
template<class ... Ts>
struct D : B<Ts...>
{

D(Ts ... datas) : B<Ts...>(datas...) { }
void foo()
{
B<Ts...> b(this->B<Ts...>::datas[]...); // ok
B<Ts...> b2(datas[]...); // ok also

get_type<0,types[]...>::type* t = nullptr; // will this be a
problem??
}
};

> > > The trouble gets compounded if you have nested pack expansions,
> > > because it isn't clear how certain expressions get captured within
> > > "...", e.g.,
>
> > I think we should clarify how expressions get captured by "..." by

This was triggered by your statement that "it isn't clear how certain
expressions get captured"
It seems that I am not sure what exactly you were requiring
clarification for then?

>
> [temp.variadic]p4 is pretty clear about what's in the pattern and what
> isn't in the pattern, and p5 covers the capturing behavior. How would
> you like to clarify this?
>

> > For e.g.
> > f( pack1 ? pack2 : pack3 ...) // only captures pack3 - need parens
> > f( pack1 == pack2 ... ) // only captures pack2 - need parens
>
> This would be an extremely unfortunate side effect if the change you
> are proposing. Right now, the "..." captures the entire call argument,
> and I think that's an important property.
>

Well the reason i think it's important not to have it capture the
entire sequence of tokens
preceding the ellipsis, is because if it does capture the entire
sequence of tokens, then
getting the logic for the separator is very hard in arbitrary
expression expansions (assuming we
want this in our language).

For e.g. take:

return pack0 && pack1 == pack2 ...

I would want this expanded as: return pack0$1 && (pack1$1 == pack2$1)
&& pack0$2 && (pack1$2 == pack2$2) ...


But I am not sure the current language allows me to specify that
intent - which means that
the separator would have to be a comma and the expansion would be as
follows (with or without parens)
pack0$1 && pack1$1 == pack2$1, pack0$2 && pack1$2 == pack2$2 ...


What I would like to specify is that if the pattern is an operand to a
binary operator then
that binary operator be used as the syntactic separator - if you feel
there is a way to accomplish
this with the current wording, then I retract my proposal for
rewording the "..."
capture principle.


Also, why do you see the requirement of parentheses here as so
unfortunate?
f (pack1 == pack2) ... ) or f( (pack1 ? pack2 : pack3)...) doesn't
seem too bad.


> > Note - some of the above would need a rewording of the verbiage for
> > selecting the appropriate syntactic separator - but i think using the
> > idea that if the pattern is an operand of a binary operator, using its
> > syntactic category for expansion should work.
>
> I'm a bit concerned about the change in behavior that you showed
> above. Is there any other way?

If there is another way, I would be most interested in seeing how it
could be accomplished.

> > Why can't we just have the following be specified in the standard
> > and have their implementation be some compiler magic that guarantees
> > constant access? (along the lines of what we specify with certain
> > compile time traits)
>
> > std::pack_get_type<N,Types...>::type // returns the expected type ...
> > std::pack_get<N>(args...) // returns the expected l-value ...
>
> That's certainly possible. It's also small enough that it might be
> able to still slip into C++0x. Of course, this works for function
> parameter packs and template type parameter packs, but not template
> template or template non-type parameter packs.
>


Good point. I'll have to give this some thought - but I get the sense
that people better suited for this than me have thought long and hard
about this and have been unsatisfied with their resulting options.


> > Also would we need special syntax to specify a certain member
> > declaration pack requirement in a certain
> > concept ?
>
> Yes, we would probably need special syntax.
>

> No, metaprogramming tricks don't work within the bodies of concepts,
> because they can't be type-checked at template definition time.


Hmm - doesn't that mean there is no way for a concept to specify that
a class
have a certain data member? This was by design I take it? What was
the rationale for that?

thanks,


Faisal Vali
Radiation Oncology
Loyola

--

Faisal Vali

unread,
Jan 15, 2009, 2:52:00 AM1/15/09
to
On Jan 13, 1:17 pm, Douglas Gregor <doug.gre...@gmail.com> wrote:
> On Jan 13, 2:13 am, Faisal Vali <fais...@gmail.com> wrote:
>
> > > > 3) A guaranteed non-recursive way to access elements of parameter
> > > > packs [edit: in constant time]

> > > > template<int N, class ... V> struct get_type
> > > > {
> > > > typedef V@N type; // or implementation_defined<N,V...>::type -
> > > > guaranteed linear
> > > > };
> > > > template<int N, class ... V> get_type<N,V...>::type get(V...v) {
> > > > return ::implementation_defined<N>(v...);
> > > > }
>
> > > This is probably the most-requested feature for variadic templates,
> > > and it never it made it because we never found a good, unambiguous
> > > syntax.


Well I was looking over the original variadic proposals - and was
wondering what the ambiguity would be here:

PP[].[N] - to uniformly access any pack element - using typename and
template appropriately to disambiguate in type dependent expressions.

As an example, consider:


template<class TTTuple, class NTTuple, class TTuple> struct S;
// definitions for the appropriate tuples

template<template<class...> ... class TTs, class ... Ts, int ... Ns>
struct S<::TTTuple<TTs...>, ::NTTuple<Ns...>, ::TTuple<Ts...>>
{
typedef ::TTTuple<TTs...> TTTuple;
typedef ::NTTuple<Ns...> NTTuple;
typedef ::NTTuple<Ns...> TTuple;

template<class ... T1s> using FirstTT = TTs[].[0]<T1s...>; /*
use .template [M] if TDE (type dep exp) */
typedef Ts[].[0] FirstT; // use typename if TDE
enum { first = Ns[].[0] };

Ts ... members;
template<class ... Args>
void setFirst(Args... args)
{
members[].[0] = args.[0]; // should args[].[0] be allowed for
symmetry to refer to all packs?
}

};


Yes - it's ugly - but seems to work.

My other question is would the committee ever consider syntax such as
the following?

pp[]|N|

pp[|N|]
pp[<N>]

or something with a $, @ or ! in it?

thanks,


Faisal Vali
Radiation Oncology
Loyola

--

Reply all
Reply to author
Forward
0 new messages