Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[RFC] Variadic templates

8 views
Skip to first unread message

Douglas Gregor

unread,
Sep 2, 2006, 8:17:24 PM9/2/06
to
Variadic templates are templates that can take an arbitrary number of
"extra" template arguments. Most templates take a fixed number of
parameters. For example, an STL map has 4 parameters (Key, Data,
Compare, Alloc) while an STL pair has two parameters (T and U). But
what happens when you want to generalize a pair into a tuple (for any
N), like in the Boost Tuples library or Library TR1 "tuple" facility?
You really need tuple to take any number of template arguments, so that
one can use tuple<int, float>, tuple<char, short, int, long>, or even
tuple<>: variadic templates allow you to do so, by creating class and
function templates that accept any number of arguments. Variadic
templates also allow you to perform transformations on all of these
arguments in one step, turning many non-trivial metaprograms into
simple one-line statements and eliminating a great deal of code
repetition.

Variadic templates are a relatively small but very powerful extension
to C++. With variadic templates, we can implement a completely
type-safe printf() facility that, unlike the C printf(), works equally
well for user-defined and built-in types. We have also been able to
implement several facilities in Library TR1 and Boost without resorting
to preprocessor metaprogramming, external generators, or manual code
repetition. In particular, we have implemented the tuple, function, and
bind libraries, and using variadic templates their implementations are
significantly shorter than without.

We intend to bring variadic templates to the C++ committee meeting in
Portland and propose their inclusion in C++0x. Before that, however, we
would like some feedback from the C++ community. Additional information
about variadic templates, including a complete implementation in GCC,
is available here:


http://www.generic-programming.org/~dgregor/cpp/variadic-templates.html

A brief introduction to variadic templates:
http://www.generic-programming.org/~dgregor/cpp/brief-intro.pdf

The variadic templates proposal:

http://www.generic-programming.org/~dgregor/cpp/variadic-templates.pdf


Cheers,
Doug Gregor
doug....@gmail.com

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

hossei...@gmail.com

unread,
Sep 4, 2006, 7:16:58 PM9/4/06
to
Hi Doug,

Well, a very important advantage this may bring to us, and which AFAICS
you haven't mentioned, is that it removes the bothers resulting when --
as extensions -- implementations add template parameters to the
standard ones. In case I'm not clear this is what I mean:

Standard says:

template <class T, class Allocator = allocator<T> >
class vector;

And, there well may be -- if not already existing -- compilers which
add parameters like:

template <class T, class Allocator = allocator<T>, class myParameter>
class vector;

As Nico (Jossuttis) and and Vandervoorde also mention in C++ Templates,
this is fully standard-conforming. But, unfortunately, as they also
mention again, this may cause protability problems. Variadic Templates
will remove this. Or, I hope so at least.

Despite that, I have my own worries if this won't overcomplicate C++
which even right now needs simplification. Or, his majesty, will dye
out... :(

(I'm in a hurry, so I can't revise... :( )

TTFN,
--Hossein

Andrei Alexandrescu (See Website For Email)

unread,
Sep 4, 2006, 7:48:33 PM9/4/06
to
Douglas Gregor wrote:
> Variadic templates are templates that can take an arbitrary number of
> "extra" template arguments.
[...]

> We intend to bring variadic templates to the C++ committee meeting in
> Portland and propose their inclusion in C++0x. Before that, however, we
> would like some feedback from the C++ community.

Very nice proposal! In line with the authors, I believe that variadic
templates are an important missing feature in C++; I personally view the
recent recrudescence of preprocessor usage the most worrisome and
regressive trends in today's C++.

I have just a few nits about the proposal, dealing with mostly things
like term definitions, introduced syntax, and the such. My comments
mostly refer to the paper at
http://www.generic-programming.org/~dgregor/cpp/variadic-templates.pdf,
although I've also read the shorter introductory materials (to which I
also think the nits apply).

* The largest problem I see is that the paper doesn't clarify that it
introduces a new entity. The name of a template parameter pack is NOT a
type, not a value, and cannot be reduced to anything in existing C++.
I'm sure all of the proposal authors have internalized that to the point
they'd find it silly to sit down and clarify that in writing, but just
look at this piece of text:

"The partial specialization is defined on template type parameters T and
Args."

That implies that "Args" is a type parameter, so I could to do it
anything I could do with today's type parameters, such as:

typedef U Args;

My interpretation of the paper is that it's illegal to do that, but the
paper doesn't make that clear; in fact, the quoted paragraph---due to
the ambiguous terminology it uses---suggest that I could.

So: I'd love to see it black on white that a parameter pack is a new
kind of an entity, subject to (only?) the ... operation.

* Terminology imprecisions abound in the current proposal. For example,
in 2.1, the paper says: "The ellipsis operator (...) indicates..." The
comment refers to the code:

template <typename... Elements> class tuple;

In C++, traditionally an operator operates on values, but there are no
values here. Even if we try to use a math-inspired definition, we can't
draw one (or at least I can't), since Elements is not an existing
entity, it's a newly-introduced entity. So I'd say that use of ellipsis
here is "syntax" or "punctuation" that clarifies what Elements stands for.

* A simple copyediting pass will weed out mistakes such as "...indicates
that we any number..."; I'll stop focusing on that for the rest of the
paper, although I found some other little mistakes like that.

* Ambiguity is present again in:

"We call Elements a template parameter pack, because it packs together
any number of template arguments. With the integral types typedef above,
the Elements parameter pack would get bound to a list of template
arguments: <char, short, int, long, long long>. We will use angle
brackets to denote a list of template arguments packed into a template
parameter pack."

I find usage of angle brackets here unfortunate, because it's also
plausible C++ syntax. But it's just metanotation for the feature, and it
never appears per se in any program fragment. Confusions between
metanotation and notation are an ongoing challenge in many PL texts, and
they all at least are careful enough to distinguish them by font so as
to clarify things for the reader. So you may want to denote separators
in a different way, such as French quotes "<<" ">>" or one of the many
other nice math brackets that TeX has.

* Nit: consider unsigned in the example:

template<typename T, int PrimaryDimension, int... Dimensions>

* My unhappiness with the chosen wording can be summarized in the
following quote:

"Here we are using the ellipsis operator to pack or unpack a template
parameter pack."

So let's see. We have a pack, why in the world do we have to pack it
again? (To say nothing about using "pack" as both a noun and a verb
closely and repeatedly throughout the paper.)

My feeling is that this comes from not being firm enough in stating that
template parameter packs are a distinct entity, and clearly defining the
operations that come along with it. If that happened, maybe some
redundant unpacking and "uberpacking" (meaning, to pack a pack that was
already packed) could have been avoided. For example:

template<typename T, typename... Args>
struct count<T, Args...> {
static const int value = 1 + count<Args...>::value;
};

In here, we know Args is NOT a type, but it's a template parameter pack
(TPP). Then, without risk of ambiguity, can't we drop the "...", can't
we? There's no other possible use of Args within a template:

template<typename T, typename... Args>
struct count<T, Args...> {
static const int value = 1 + count<Args>::value;
};

If there were reasons that made this undesirable, the paper doesn't
clarify them, thus leaving minimalists unhappy.

* "This is an instance of packing a template parameter pack." If
anything sounds wrong to you, perhaps a more sensible way to express the
meaning would be: "This is an instance of packing several template
arguments into a template parameter pack." Or: "This is an instance of
creating a template parameter pack by packing together several template
arguments." No?

* "Thus, in our instantiation of count<char, short, int> (where
Args=<short, int>), count<Args...> expands to count<short, int>." The
paper leaves us wondering what count<Args> would mean. Using the paper's
own wanting terminology, it would mean that I pass the pack
"un-unpacked" to the count template. Is that bad? Not necessarily,
because wait, the template expects a pack! So that's kind of an exact
match pack-pack. Why, then, in the world do we need to unpack the thing
just to repack it on the other side? Why can't we just pass the pack?

* About the tuple example in 2.2: the example has tuple<char, int,
double> privately inherit tuple<int, double> and so on. Nice. Yet the
interesting case is one of structural subtyping, in which tuple<char,
int, double> inherits (publically this time) tuple<char, int> which
inherits tuple<char>. Then I can pass a "larger" tuple when a "smaller"
tuple is expected, which is sound. But it's unclear on how to do that,
and an example on how to do it would be most instructive. I have an idea
on how to do it by first defining a Reverse<...> helper, but maybe more
elegant means are possible and desirable. For example, matching
something at the end of a parameter pack?

* "Variadic templates build upon the ideas of C’s variable-length
function parameter lists." I fail to see any relationship, and
mentioning one based on whatever slight resemblance does nothing but
diminish the merit of the paper by associating it with what's widely
considered an ill-designed feature. The paper's approach is to generate
at compile time a family of overloaded functions, in stark contrast with
C's approach of allowing anything and then trying to figure out the
stack layout by means of conventions.

* "the three function call arguments will be packed, so args = (17,
3.14159, string(‘‘Hello!’’))." This notation introduced without warning
is, again, confusing. The round parens are in code font, yet they are
part of the metanotation (which, worse, is introduced without a proper
definition) so the reader might thing that args = (17, 3.14159,
string("Hello!")) might be a valid C++ assignment, which I think isn't -
but again, the paper doesn't clarify that. Such repeated notational
scaffolding introduced the minute it's needed without rigor can be quite
tiresome for anyone trying to give the paper more than a casual read.

* In the printf example, again it's unclear why printf(++s, args...) is
necessary when printf(++s, args) would be just as unambiguous and less
baroque.

* "For instance, our printf() function above should really have taken
its arguments be const reference, because they won’t be modified."
That's not the reason. The reason is: "For instance, our printf()
function above should really have taken its arguments be const
reference, because the cost of copying them might be arbitrarily
expensive. This is a side effect of our printf supporting user-defined
types in addition to built-in types."

* The reader starts understanding the necessity of the "..." operator no
earlier than page 6, when it becomes (to those with detectivistic
inclinations) clear that "..." applies to the expression to its left and
expands it for each parameter in a pack, a la Scheme macros. Is that so?
If so, why isn't that simply stated, and earlier in the document if
possible? It's a very powerful features.

* "Template parameter pack expressions can occur at the end of any
template argument list." Imprecise language hits again. They aren't
expressions!


That's pretty much all... again, great work!


Andrei

Paul Mensonides

unread,
Sep 5, 2006, 3:20:27 AM9/5/06
to
Andrei Alexandrescu (See Website For Email) wrote:

>> We intend to bring variadic templates to the C++ committee meeting in
>> Portland and propose their inclusion in C++0x. Before that, however,
>> we would like some feedback from the C++ community.
>
> Very nice proposal! In line with the authors, I believe that variadic
> templates are an important missing feature in C++; I personally view
> the recent recrudescence of preprocessor usage the most worrisome and
> regressive trends in today's C++.

On the contrary, such bias is both regressive and limiting. If C++ started
differently, the syntax might have been more uniform and more amenable to the
integration of code generation macros such as those in Lisp or Scheme. At this
point, however, we have to take and use what we have and possibly extend the
language in minor ways that make certain common things easier. There is no
doubt that variadic templates are a step in the right direction, and they can
significantly decrease the amount of necessary preprocessor metaprogramming.
That is a good thing, but not because of its relation to preprocessor
metaprogramming, but rather because they will state more precisely the intent of
the programmer by virtue of being less elaborate. However, variadic templates
do not have the power to eliminate (by means of better alternative) preprocessor
metaprogramming, they only have the power to minimize its use in a certain area.
As I said, that is a good thing--but not because it mitigates the use of the
preprocessor but because it is a *better* solution to a particular class of
problems. I've been a supporter of variadic templates from the beginning, but
preprocessor metaprogramming is necessary now and will still be necessary even
if we get variadic templates simply because the language has no other facility
capable of *writing* the underlying language (as Lisp and Scheme do to an
extent). That won't change with just arbitrary numbers of parameters--either of
functions or templates, and it won't change with a few other bolt-on language
features for certain tasks. It would only change with the addition of a
comprehensive generation, transformation, and reflection package that is capable
of, in essence, *creating* language features intentionally--which is unlikely to
happen (not just soon, but at all) in C++. Templates aren't good enough,
neither are macros, but the lack of a comprehensive solution necessitates that
we use them, often together, to achieve a particular purpose. Moreover, we use
them in well-structured ways based on idiom (as opposed to restriction)--just
like we do in any other combination of language features in any language.
Unless you can show better alternatives for all scenarios that require it now
(even with variadic templates), any argument calling it a "worrisome and
regressive trend" is utterly devoid of any worthwhile substance. Such bias,
toward any idiom, is actually far more regressive and harmful in the long term
than any badly applied idiom. It inhibits the creative application of the tools
that we have now--stifling the creativity that produces new idioms, ideas, and
techniques--either in favor of some idealistic "should be" that we will never
have or in favor of a misplaced fear of the potential of misuse. Neither of
which is objective or practical. Without preprocessor metaprogramming, many of
the use cases for variadic templates would not have enough concrete evidence of
real-world application that they now have. Furthermore, with the introduction
of variadic templates many components that are partially or totally generated
with preprocessor metaprogramming can be reimplemented transparently--without
changing the interface or client code--which shows the extant viability of the
tool right now for this particular class of problems, and is indicative of its
viability in general. Templates are a powerful tool, and useful, and they will
be more useful with variadics, but they are limited in some fundamental ways.
The preprocessor is also a powerful tool, and useful, but it is limited in
different fundamental ways. They are complementary, and their combination is
superior to either alone. If you add another language feature, it will only add
another possible construct to generate with the preprocessor. To supplant it
requires providing alternative solutions for everything that it can do that are
better--both objectively and subjectively. That requires integrated language
features capable of, at minimum, producing and manipulating syntactic patterns
that are parametized by syntactic patterns (inclusive of the entire underlying
language--which implies that the features capable of this are recursively
manipulatable). That requires something far more comprehensive than a relatively
minor improvement to the template mechanism.

Regards,
Paul Mensonides

Bronek Kozicki

unread,
Sep 5, 2006, 10:51:07 AM9/5/06
to
hossei...@gmail.com wrote:
> Despite that, I have my own worries if this won't overcomplicate C++
> which even right now needs simplification. Or, his majesty, will dye
> out... :(

I'm not worried about that. If this facility makes MY programs simpler,
safer and easier to maintain (and it definitely seems it will), I want
it. The same applies to other features planned for C++0x, eg. concepts.

It is not that much important how complex language is (C++ crossed the
line in its first ISO standard edition anyway), but how difficult to
write and maintain programs written in this language are. If some new
feature makes it easy to write better programs and plays nice with
existing language features, then it is definitely worth considering.


B.

Niklas Matthies

unread,
Sep 5, 2006, 2:50:05 PM9/5/06
to
On 2006-09-04 23:48, Andrei Alexandrescu (See Website For Email) wrote:
:

> * "This is an instance of packing a template parameter pack." If
> anything sounds wrong to you,

Isn't it correct and common to speak of "packing a packet"?
So what's wrong with packing a pack (from certain elements)?
There might be opportunity for ambiguity if context is lacking,
but otherwise I think it's perfectly fine.

:


> * About the tuple example in 2.2: the example has tuple<char, int,
> double> privately inherit tuple<int, double> and so on. Nice. Yet the
> interesting case is one of structural subtyping, in which tuple<char,
> int, double> inherits (publically this time) tuple<char, int> which
> inherits tuple<char>. Then I can pass a "larger" tuple when a "smaller"
> tuple is expected, which is sound.

Is it? Normally structural subtyping isn't applied to tuples that way.
While <int, int> is a subtype of the type (scheme) <int, ...>, it is
not really a subtype of <int> (although <int> is as well a subtype of
<int, ...>). One practical reason is that having elements silently
dropped is generally a bad thing. A more important reason is that
equality becomes non-transitive: <3, 4> != <3, 5> as instances of
<int, int> but <3, 4> == <3, 5> as instances of <int> because <3> ==
<4>. Worse, since every tuple type becomes a subtype of the empty
tuple, all tuples suddenly become equal if transitivity is applied,
since the empty tuple is equal to itself.

-- Niklas Matthies

Bronek Kozicki

unread,
Sep 5, 2006, 2:36:55 PM9/5/06
to
"Bronek Kozicki" <br...@spam-trap-cop.net> wrote:
> It is not that much important how complex language is (C++ crossed the
> line in its first ISO standard edition anyway), but how difficult to
^^^^^^^^^
I meant "easy" here.

Just to make myself clear: I consider variadic templates far superior
to Boost.MPL, Loki or my own typelists solutions, all based on
metaprogramming and macros hackery. This hackery is a visible proof
that variadic templates proposal *is* addressing real problems in C++ .


B.

--
Remove -trap- when replying. Usun -trap- gdy odpisujesz.

Andrei Alexandrescu (See Website For Email)

unread,
Sep 5, 2006, 7:39:45 PM9/5/06
to
Niklas Matthies wrote:
> On 2006-09-04 23:48, Andrei Alexandrescu (See Website For Email) wrote:
>>* About the tuple example in 2.2: the example has tuple<char, int,
>>double> privately inherit tuple<int, double> and so on. Nice. Yet the
>>interesting case is one of structural subtyping, in which tuple<char,
>>int, double> inherits (publically this time) tuple<char, int> which
>>inherits tuple<char>. Then I can pass a "larger" tuple when a "smaller"
>>tuple is expected, which is sound.
>
>
> Is it? Normally structural subtyping isn't applied to tuples that way.

I believe it is, and I tried to reduce typing by glossing over this
discussion. Paradoxically, the "other" way is sound as well, as I'll try
to outline below :o).

> While <int, int> is a subtype of the type (scheme) <int, ...>, it is
> not really a subtype of <int> (although <int> is as well a subtype of
> <int, ...>). One practical reason is that having elements silently
> dropped is generally a bad thing. A more important reason is that
> equality becomes non-transitive: <3, 4> != <3, 5> as instances of
> <int, int> but <3, 4> == <3, 5> as instances of <int> because <3> ==
> <4>. Worse, since every tuple type becomes a subtype of the empty
> tuple, all tuples suddenly become equal if transitivity is applied,
> since the empty tuple is equal to itself.

It all depends if you think of tuple<Types> as the open-ended record
(word that I use here with the meaning of "bag-of-public-values" akin to
a POD) prefixed by members of types Types, or the exact record
containing values of types Types. Both approaches are sound. The latter
is trivial and less interesting, the former can be justified by means of
an example.

Consider a record Point aggregating coordinates x and y. Then,
ColoredPoint might inherit from it adding a variable Color. Moreover, if
you work with Points and ColoredPoints in a monocolor projection, you do
want two points to compare equal even though one or both of them contain
color information. That's what you achieve by comparing them via Point's
notion of equality. Yes, it might look like equality is not transitive
anymore, but that's ignoring part of the story, which involves
voluntarily shunting the comparison operation someplace along the line.
Equality is still transitive, as long as it's used properly. (I agree
with the argument that that might not be always obvious and expected,
but it's still sound.)

When would such an approach break? As soon as we're dealing with
invariants and mutation. But tuples are records with all public members
therefore not pretending to enforce any intrinsic invariant, so mutation
after converting to a supertype doesn't affect soundness.


Andrei

Andrei Alexandrescu (See Website For Email)

unread,
Sep 5, 2006, 7:33:54 PM9/5/06
to
Bronek Kozicki wrote:
> "Bronek Kozicki" <br...@spam-trap-cop.net> wrote:
>
>> It is not that much important how complex language is (C++ crossed the
>> line in its first ISO standard edition anyway), but how difficult to
>
> ^^^^^^^^^
> I meant "easy" here.
>
> Just to make myself clear: I consider variadic templates far superior
> to Boost.MPL, Loki or my own typelists solutions, all based on
> metaprogramming and macros hackery. This hackery is a visible proof
> that variadic templates proposal *is* addressing real problems in C++ .

Definitely. By the way, the authors might also add Modern C++ Design and
Loki to the pile of justificative previous work. Most of the gratuitous
complications MC++D and Loki go through to implement and use typelists
would be radically reduced by the proposed facility.

Andrei

kwikius

unread,
Sep 6, 2006, 12:23:14 AM9/6/06
to

> ColoredPoint might inherit from it adding a variable Color.

I would just like to make one small point, considering especially that
Andrei Alexandrescu is a stickler for precision in matters of logic. A
point is by definition, infinitessimally small and therefore cannot
have a colour. There is no surface there to be coloured.

regards
Andy Little

Andrei Alexandrescu (See Website For Email)

unread,
Sep 5, 2006, 11:49:50 PM9/5/06
to
kwikius wrote:
>>ColoredPoint might inherit from it adding a variable Color.
>
>
> I would just like to make one small point, considering especially that
> Andrei Alexandrescu is a stickler for precision in matters of logic. A
> point is by definition, infinitessimally small and therefore cannot
> have a colour. There is no surface there to be coloured.
>
> regards
> Andy Little

Thanks, Andy.Little corrections are always welcome, as are innocuous
puns I hope :o). Let's replace Point with Pixel throughout, and let's
replace color with colour, too. We could even use the preprocessor to
help with that :oD.

Andrei

Alec Ross

unread,
Sep 6, 2006, 10:49:04 AM9/6/06
to
In message <1157502178.9...@d34g2000cwd.googlegroups.com>,
kwikius <an...@servocomm.freeserve.co.uk> writes

>
>> ColoredPoint might inherit from it adding a variable Color.
>
>I would just like to make one small point, considering especially that
>Andrei Alexandrescu is a stickler for precision in matters of logic. A
>point is by definition, infinitessimally small and therefore cannot
>have a colour. There is no surface there to be coloured.
>
Nope, but the concept still makes sense. That is, a cp is an instance
of a type that supports at least some of a "point-like", interface, and
also has an additional interface for something called colour/color.

(Cf the use of colour as an attribute in the physics sub-atomic
particles.)

In the context of typical OO-101 examples, and computer graphics
implementations, there is a history of the use of cp s, which I take be
behind Andrei's illustration.

Often in the OO examples people derive Cp from a Point class - though I
tend prefer aggregation. (With "actual", or rendering size, or even
shape yet another attribute(s).) To my taste this keeps the Point-ness
a separate mathematical notion, as you seem to like as well.

Sure, in many cases such Cp types would better be called "Pixel"s, or
whatever. But, my point (;)) is that the notion of a cp, as a point w/
colour can make conceptual/analytical sense as well.

Best,

Alec
--
Alec Ross

Howard Gardner

unread,
Sep 6, 2006, 1:18:05 PM9/6/06
to
Douglas Gregor wrote:
> Variadic templates are templates that can take an arbitrary number of
> "extra" template arguments. Most templates take a fixed number of
> parameters. For example, an STL map has 4 parameters (Key, Data,
> Compare, Alloc) while an STL pair has two parameters (T and U). But
> what happens when you want to generalize a pair into a tuple (for any
> N), like in the Boost Tuples library or Library TR1 "tuple" facility?
> You really need tuple to take any number of template arguments, so that
> one can use tuple<int, float>, tuple<char, short, int, long>, or even
> tuple<>: variadic templates allow you to do so, by creating class and
> function templates that accept any number of arguments. Variadic
> templates also allow you to perform transformations on all of these
> arguments in one step, turning many non-trivial metaprograms into
> simple one-line statements and eliminating a great deal of code
> repetition.

This addresses one of the stumbling blocks with templates in the current
language, and the concepts address others, but there are more.

For an example, consider implementing a facility to create a string that
uniformly names types:

template< typename T > struct uniform_name{ static string invoke(); };

so I'd like to be able to write uniform_name< int >::invoke() and get
back the string "int"

Or, for the template class template< long > example; write
uniform_name< example< 20 > >::invoke() and get back the string
"example< 20 >".

Or, write uniform_name< std::string >::invoke() and get back the string
"std::string< char, std::char_traits< char >, std::allocator< char > >".

I've partially implemented this facility. The implementation is hard
and (development time) expensive. All of the hardness comes from
irregularities in the way that templates work, and so does much of the
expense.

The minimum code instanced on the subject type is something to return
the type ("int") or class name ("example", "std::string"). That would be
pretty cheap to do. In point of fact, much more than that ends up
instanced on the subject type. It's not all for one reason, either: its
the death of a thousand cuts.

These are the particular stumbling blocks that hurt the worst, and I run
into them fairly often (or, more correctly, I deploy workarounds for
them fairly often).

1) If you write:

template< typename T, T V > struct sometype;

then there's no way to partially specialize it for a particular type. It
seems to me that this should work (but of course it doesn't).

template< int V > struct sometype< int, V >;

2) There is no way to specify a generic nontype template parameter. This
would be nice:

template< nontype > struct something;
template< > struct something< char * >;
template< typename T > something< T * >;
template< typename T > something< T ** >;

so I can write a template that will take an integer, a particular type
of pointer, a reference, a pointer to a pointer to anything, etc.

3) The template parameter arity of template template parameters. Maybe
variadic templates will fix that. Is this meant to work? (It's the
template template parameter that's variadic.)

template< template< typename... > class > struct something;

4) A particular template parameter can be a type, a nontype, or a
template. There's no way to write it so that it can be any of them.
If something like this were feasible, it would help a lot:

template< auto > struct something;

template< > struct something< int >;
// pretend auto was "typename"

template< typename > dummy;
// going to use this in a second

template< > something< dummy >;
// pretend auto was "template< typename > class"

template< typename, typename > dummy2;
// going to use this in a second

template< > something< dummy2 >;
// pretend auto was "template< typename, typename > class"

template< > struct something< 5 >;
// pretend auto was "int", and let me specialize on 5.

What I'm after here is some way to say "this is a template taking one
parameter" without saying anything else about it.

Of course, the next thing I'll want is:

template< auto... > struct something;

but I suppose I'll wait until I actually need it ;)

Douglas Gregor

unread,
Sep 6, 2006, 1:27:19 PM9/6/06
to

Andrei Alexandrescu (See Website For Email) wrote:
> Douglas Gregor wrote:
> > Variadic templates are templates that can take an arbitrary number of
> > "extra" template arguments.
> [...]
> > We intend to bring variadic templates to the C++ committee meeting in
> > Portland and propose their inclusion in C++0x. Before that, however, we
> > would like some feedback from the C++ community.
>
> Very nice proposal! In line with the authors, I believe that variadic
> templates are an important missing feature in C++; I personally view the
> recent recrudescence of preprocessor usage the most worrisome and
> regressive trends in today's C++.
>
> I have just a few nits about the proposal, dealing with mostly things
> like term definitions, introduced syntax, and the such. My comments
> mostly refer to the paper at
> http://www.generic-programming.org/~dgregor/cpp/variadic-templates.pdf,
> although I've also read the shorter introductory materials (to which I
> also think the nits apply).

Wonderful. Thank you for the detailed comments; where I don't respond
to something below, assume I'm nodding my head in agreement and
resolving to improve things in the next draft.

> typedef U Args;
>
> My interpretation of the paper is that it's illegal to do that, but the
> paper doesn't make that clear; in fact, the quoted paragraph---due to
> the ambiguous terminology it uses---suggest that I could.

Yep, your interpretation is correct, and the paragraph quoted is very
ambiguous.

> * My unhappiness with the chosen wording can be summarized in the
> following quote:
>
> "Here we are using the ellipsis operator to pack or unpack a template
> parameter pack."
>
> So let's see. We have a pack, why in the world do we have to pack it
> again? (To say nothing about using "pack" as both a noun and a verb
> closely and repeatedly throughout the paper.)
>
> My feeling is that this comes from not being firm enough in stating that
> template parameter packs are a distinct entity, and clearly defining the
> operations that come along with it. If that happened, maybe some
> redundant unpacking and "uberpacking" (meaning, to pack a pack that was
> already packed) could have been avoided. For example:
>
> template<typename T, typename... Args>
> struct count<T, Args...> {
> static const int value = 1 + count<Args...>::value;
> };
>
> In here, we know Args is NOT a type, but it's a template parameter pack
> (TPP). Then, without risk of ambiguity, can't we drop the "...", can't
> we? There's no other possible use of Args within a template:
>
> template<typename T, typename... Args>
> struct count<T, Args...> {
> static const int value = 1 + count<Args>::value;
> };
>
> If there were reasons that made this undesirable, the paper doesn't
> clarify them, thus leaving minimalists unhappy.

This certainly needs clarification. The ellipsis is necessary because
we need to know where to expand the parameter pack. We don't always
want to expand it immediately, as in the count example. Let's look at a
metafunction "one_tuples" that creates a tuple of 1-tuples, e.g.,
one_tuples<int, float>::type would be tuple<tuple<int>, tuple<float> >.
We can write this metafunction as:

template<typename... Args>
struct one_tuples {
typedef tuple<tuple<Args>...> type;
};

This works with the current spec, but if we added the immediate
expansion of "Args" (like we needed for the shortened "count"), it
would result in the equivalent of:

typedef tuple<tuple<Args...>...> type;

The second ellipsis would then be an error. There is a more complicated
rule that would make both the shortened count and one_tuples work, but
implementing it involves a great deal of backtracking in the compiler.
That wouldn't fit into the one-week development time :)

> * About the tuple example in 2.2: the example has tuple<char, int,
> double> privately inherit tuple<int, double> and so on. Nice. Yet the
> interesting case is one of structural subtyping, in which tuple<char,
> int, double> inherits (publically this time) tuple<char, int> which
> inherits tuple<char>. Then I can pass a "larger" tuple when a "smaller"
> tuple is expected, which is sound. But it's unclear on how to do that,
> and an example on how to do it would be most instructive. I have an idea
> on how to do it by first defining a Reverse<...> helper, but maybe more
> elegant means are possible and desirable. For example, matching
> something at the end of a parameter pack?

I can't think of anything more elegant than using Reverse. As you note,
matching at the end of a parameter pack would work, e.g.,

template<typename... Head, typename Last>
struct tuple<Head..., Last> : public tuple<Head...> {
// rest of the implementation
};

When we looked at this before, we decided that the cost of this feature
was not worth its gain. Allowing deduction anywhere but at the end of
the parameter list opens up a can of worms that we weren't sure how to
close. Consider, for instance:

template<typename... Args1, typename... Args2>
void foo(Args1... args1, int middle, Args2... args2);

Eeep!

> * "Variadic templates build upon the ideas of C's variable-length
> function parameter lists." I fail to see any relationship, and
> mentioning one based on whatever slight resemblance does nothing but
> diminish the merit of the paper by associating it with what's widely
> considered an ill-designed feature.

Hah. Good point. I might as well say that the implementation of a new
feature is "slightly less complicated than export" :)

> * In the printf example, again it's unclear why printf(++s, args...) is
> necessary when printf(++s, args) would be just as unambiguous and less
> baroque.

Right; the ellipsis is mandatory here for the same reason as above.

Thanks again for the detailed comments!

Cheers,
Doug

Niklas Matthies

unread,
Sep 6, 2006, 1:56:46 PM9/6/06
to
On 2006-09-05 23:39, Andrei Alexandrescu (See Website For Email) wrote:
> Niklas Matthies wrote:
:

>> While <int, int> is a subtype of the type (scheme) <int, ...>, it is
>> not really a subtype of <int> (although <int> is as well a subtype of
>> <int, ...>). One practical reason is that having elements silently
>> dropped is generally a bad thing. A more important reason is that
>> equality becomes non-transitive: <3, 4> != <3, 5> as instances of
>> <int, int> but <3, 4> == <3, 5> as instances of <int> because <3> ==
>> <4>. Worse, since every tuple type becomes a subtype of the empty
>> tuple, all tuples suddenly become equal if transitivity is applied,
>> since the empty tuple is equal to itself.
>
> It all depends if you think of tuple<Types> as the open-ended record
> (word that I use here with the meaning of "bag-of-public-values" akin to
> a POD) prefixed by members of types Types, or the exact record
> containing values of types Types. Both approaches are sound. The latter
> is trivial and less interesting, the former can be justified by means of
> an example.

Well, but a tuple isn't a "bag of public values". In particular, an
n-tuple is a sequence of _exactly_ n values, not a sequence of n or
more values. Hence an n-tuple is never an (n+1)-tuple, and vice versa.
What you are talking about is a '(at least n)-tuple' type (what I
denoted with the "..." syntax about), basically the union of all tuple
types with the specified particular prefix, but that's a very
different beast from an n-tuple type.

> Consider a record Point aggregating coordinates x and y. Then,
> ColoredPoint might inherit from it adding a variable Color. Moreover, if
> you work with Points and ColoredPoints in a monocolor projection, you do
> want two points to compare equal even though one or both of them contain
> color information. That's what you achieve by comparing them via Point's
> notion of equality. Yes, it might look like equality is not transitive
> anymore, but that's ignoring part of the story, which involves
> voluntarily shunting the comparison operation someplace along the line.

I'm not sure what you mean by shunting. This is a matter of LSP.
Whether you look at two ColoredPoints as Points or as ColoredPoints
shouldn't change their notion of equality. If ColoredPoint is a tuple
type, i.e. 'Coordinate × Coordinate × Color', then equality certainly
must include the Color element. Conversely, if Point is the tuple type
'Coordinate × Coordinate', then equality on Points cannot include
consideration of a Color. The only way to bring both together is to
have a type X := 'Coordinate × Coordinate × ...'. Then both Point :=
'Coordinate × Coordinate' and ColoredPoint := 'Coordinate × Coordinate
× Color' are subtypes of X. But X is not a tuple type; it's the union
of all tuple types with the prefix 'Coordinate × Coordinate'.

-- Niklas Matthies

Andrei Alexandrescu (See Website For Email)

unread,
Sep 6, 2006, 3:57:28 PM9/6/06
to
Douglas Gregor wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>>In here, we know Args is NOT a type, but it's a template parameter pack
>>(TPP). Then, without risk of ambiguity, can't we drop the "...", can't
>>we? There's no other possible use of Args within a template:
>>
>>template<typename T, typename... Args>
>>struct count<T, Args...> {
>> static const int value = 1 + count<Args>::value;
>>};
>>
>>If there were reasons that made this undesirable, the paper doesn't
>>clarify them, thus leaving minimalists unhappy.
>
>
> This certainly needs clarification. The ellipsis is necessary because
> we need to know where to expand the parameter pack. We don't always
> want to expand it immediately, as in the count example. Let's look at a
> metafunction "one_tuples" that creates a tuple of 1-tuples, e.g.,
> one_tuples<int, float>::type would be tuple<tuple<int>, tuple<float> >.
[...]

> This works with the current spec, but if we added the immediate
> expansion of "Args" (like we needed for the shortened "count"), it
> would result in the equivalent of:
>
> typedef tuple<tuple<Args...>...> type;
>
> The second ellipsis would then be an error. There is a more complicated
> rule that would make both the shortened count and one_tuples work, but
> implementing it involves a great deal of backtracking in the compiler.
> That wouldn't fit into the one-week development time :)

I now understand. I think it would help greatly if you specified that
(and please let me know if I'm misunderstanding things): "..." to the
right of an expression or type specification involving a type or value
pack P of N elements is a metaoperator that expands that expression or
type specification into the corresponding N expressions or type
specifications, conceptually comma-separated, EXCEPT for instances of
the old meaning of "...", which sucks and stays with unchanged
semantics. ("Which sucks" shouldn't be mentioned in the proposal.)

Also, "..." to the right of (1) "typename", (2) "class", (3) an actual
type, is syntax that introduces a parameter pack. So there are really
two new uses of "...": metaoperator and syntax. (I say "metaoperator"
because it's an operator doing its job during compilation, unlike usual
operators.)

I'm not sure if my rule is correct, but it seems to cover your examples
very nicely:

- When we see printf(s, args...); I understand that "..." applies to
"args" and expands it to the comma-separated values packed inside args.

- When we see template<typename T, typename... Args> struct count<T,
Args...> {}; we known to expand the second "..." into whatever arguments
come down the pike for count.

- When we see template <typename... Args> void printf(const char *s,
const Args&...) we know that the typespec 'const Args&' will be nicely
expanded into the comma-separated typespecs.

And so on. If I just saw this rule someplace after the first simple
example, I could have navigated the more complex examples with ease. Now
I can even come up with more intricate examples that I'm not sure how
would work:

printf(s, args ? args + 5 : args * 6...);

Would this expand each arg in the pack to the expression? By the way,
the precedence of "..." should be specified - I guess the obvious choice
is to make it the bottom of the operator hierarchy, so it catches the
longest expressions.

>>Then I can pass a "larger" tuple when a "smaller"
>>tuple is expected, which is sound. But it's unclear on how to do that,
>>and an example on how to do it would be most instructive. I have an idea
>>on how to do it by first defining a Reverse<...> helper, but maybe more
>>elegant means are possible and desirable. For example, matching
>>something at the end of a parameter pack?
>
>
> I can't think of anything more elegant than using Reverse. As you note,
> matching at the end of a parameter pack would work, e.g.,
>
> template<typename... Head, typename Last>
> struct tuple<Head..., Last> : public tuple<Head...> {
> // rest of the implementation
> };
>
> When we looked at this before, we decided that the cost of this feature
> was not worth its gain. Allowing deduction anywhere but at the end of
> the parameter list opens up a can of worms that we weren't sure how to
> close. Consider, for instance:
>
> template<typename... Args1, typename... Args2>
> void foo(Args1... args1, int middle, Args2... args2);
>
> Eeep!

I understand. Looks a lot like those examples "a context-free grammar
could never match this pattern" :o).

I will duly note, however, that Reverse<...> takes linear time and
space. This is the downside of limiting the feature to right-branching only.


Andrei

Douglas Gregor

unread,
Sep 6, 2006, 6:06:06 PM9/6/06
to
Andrei Alexandrescu (See Website For Email) wrote:
> I now understand. I think it would help greatly if you specified that
> (and please let me know if I'm misunderstanding things): "..." to the
> right of an expression or type specification involving a type or value
> pack P of N elements is a metaoperator that expands that expression or
> type specification into the corresponding N expressions or type
> specifications, conceptually comma-separated, EXCEPT for instances of
> the old meaning of "...", which sucks and stays with unchanged
> semantics. ("Which sucks" shouldn't be mentioned in the proposal.)
>
> Also, "..." to the right of (1) "typename", (2) "class", (3) an actual
> type, is syntax that introduces a parameter pack. So there are really
> two new uses of "...": metaoperator and syntax. (I say "metaoperator"
> because it's an operator doing its job during compilation, unlike usual
> operators.)

Yep, this is a good rule. I had a short discussion offline with someone
you referred to the ellipsis "on the left" (meaning, left of the name
of something being declared, e.g., a template parameter pack or
function parameter pack) vs. "on the right" (meaning, right of the
expression or type). An ellipsis on the left declares a parameter pack,
an ellipsis on the right is a pack or unpack.

> I'm not sure if my rule is correct, but it seems to cover your examples
> very nicely:

Yep, your rule works well. Thanks!

> And so on. If I just saw this rule someplace after the first simple
> example, I could have navigated the more complex examples with ease. Now
> I can even come up with more intricate examples that I'm not sure how
> would work:
>
> printf(s, args ? args + 5 : args * 6...);
>
> Would this expand each arg in the pack to the expression? By the way,
> the precedence of "..." should be specified - I guess the obvious choice
> is to make it the bottom of the operator hierarchy, so it catches the
> longest expressions.

"..." has lower precedence than anything except the comma that
separates arguments in a function call, function type, or template
argument list. So your printf call above would expand to the following,
if we think of args as containing two arguments, args1 and args2:

printf(s, args1 ? args1 + 5 : args1 * 6, args2 ? args2 + 5 : args2 *
6);

> > close. Consider, for instance:
> >
> > template<typename... Args1, typename... Args2>
> > void foo(Args1... args1, int middle, Args2... args2);
> >
> > Eeep!
>
> I understand. Looks a lot like those examples "a context-free grammar
> could never match this pattern" :o).

:)

> I will duly note, however, that Reverse<...> takes linear time and
> space. This is the downside of limiting the feature to right-branching only.

You are absolutely correct.

Doug

Andrei Alexandrescu (See Website For Email)

unread,
Sep 6, 2006, 8:36:48 PM9/6/06
to
Niklas Matthies wrote:
> On 2006-09-05 23:39, Andrei Alexandrescu (See Website For Email) wrote:
>>It all depends if you think of tuple<Types> as the open-ended record
>>(word that I use here with the meaning of "bag-of-public-values" akin to
>>a POD) prefixed by members of types Types, or the exact record
>>containing values of types Types. Both approaches are sound. The latter
>>is trivial and less interesting, the former can be justified by means of
>>an example.
>
>
> Well, but a tuple isn't a "bag of public values". In particular, an
> n-tuple is a sequence of _exactly_ n values, not a sequence of n or
> more values. Hence an n-tuple is never an (n+1)-tuple, and vice versa.
> What you are talking about is a '(at least n)-tuple' type (what I
> denoted with the "..." syntax about), basically the union of all tuple
> types with the specified particular prefix, but that's a very
> different beast from an n-tuple type.

I don't have a strong conviction that "tuple" is consecrated to "exactly
n tuple" in C++ or elsewhere. Google seems to steer me otherwise,
because googling for "tuple subtyping" yields as first hit
(http://www-edlab.cs.umass.edu/cs530/LectureSlides06/subtyping-jcw06-6up.pdf#search=%22tuple%20subtyping%22)
a subtyping rule that makes wider tuples subtypes of narrow tuples. Even
more so, record subtyping (which is actually closer to what we discuss
here) in the same notes also favors the idea that dropping fields off a
record is fine. That course is on MiniML, which in this regard follows
the same rule as ML.

The second hit leads to Nemerle, which after rummaging a little I
understand doesn't support tuple subtyping by dropping fields, which
would support the other view.

So, it's unclear to me that "tuple" within a programming language
context automatically means there's no possible subtyping relationship
by dropping fields (projection). And as my example shows, that can be
useful, so people might want to define both "exact tuples" and
"open-ended tuples".

>>Consider a record Point aggregating coordinates x and y. Then,
>>ColoredPoint might inherit from it adding a variable Color. Moreover, if
>>you work with Points and ColoredPoints in a monocolor projection, you do
>>want two points to compare equal even though one or both of them contain
>>color information. That's what you achieve by comparing them via Point's
>>notion of equality. Yes, it might look like equality is not transitive
>>anymore, but that's ignoring part of the story, which involves
>>voluntarily shunting the comparison operation someplace along the line.
>
>
> I'm not sure what you mean by shunting.

By shunting I meant using different comparison operators in the process
of proving lack of transitivity for ==.

> This is a matter of LSP.
> Whether you look at two ColoredPoints as Points or as ColoredPoints
> shouldn't change their notion of equality.

I believe that this is not a matter of LSP, and that this is a
confusion. It's important to note that tuples are "exposed
representation" - they have only public data and as such no intrinsic
invariant. LSP enters in action when you want to hide representation and
enforce invariants. A Date class is not (usefully) a subclass of int *
int * int, even if it does internally represent its state as three
integers. Making Data a subtype of int * int * int has three detrimental
effects: (1) it allows indiscriminate mutation of the fields; (2) it
reveals an implementation detail and potentially makes code dependent on
it; (3) is "true but interesting" in the sense that many functions that
expect three integers are not intended to work on an actual Date. Each
of these effects are harmless to the soundness and expressiveness of a
tuple.

> If ColoredPoint is a tuple
> type, i.e. 'Coordinate × Coordinate × Color', then equality certainly
> must include the Color element.

That's by your own definition of tuple, which I showed above is not as
universal as it might seem.

> Conversely, if Point is the tuple type
> 'Coordinate × Coordinate', then equality on Points cannot include
> consideration of a Color.

The converse comes from a refuted conjecture, so I refute it as well.

> The only way to bring both together is to
> have a type X := 'Coordinate × Coordinate × ...'. Then both Point :=
> 'Coordinate × Coordinate' and ColoredPoint := 'Coordinate × Coordinate
> × Color' are subtypes of X. But X is not a tuple type; it's the union
> of all tuple types with the prefix 'Coordinate × Coordinate'.

I might also feel like defining Point = 'Coordinate × Coordinate × ...'
and be exactly where I mentioned in the first place. There is no
breakage of soundness.

So it looks like we're only having a terminology problem. You agree with
my statement that projectible tuples are sound, which is pretty much all
I care about. You only associate "tuple" with a fixed cardinality, which
as I've shown is not self-understood, but I'm fine as long as you
specify it. Then, you agree that projectible tuples are useful as well,
and you just want to give them a different name. Fine. Any ideas? :o)


Andrei

Greg Herlihy

unread,
Sep 7, 2006, 2:41:25 AM9/7/06
to
Howard Gardner wrote:

>
> These are the particular stumbling blocks that hurt the worst, and I run
> into them fairly often (or, more correctly, I deploy workarounds for
> them fairly often).
>
> 1) If you write:
>
> template< typename T, T V > struct sometype;
>
> then there's no way to partially specialize it for a particular type. It
> seems to me that this should work (but of course it doesn't).
>
> template< int V > struct sometype< int, V >;

.because the partial specialization of "sometype" does not declare a
complete type. A full or partial specialization of a class template
needs to be a complete type in order to instantiate objects with it:

template< int V >
struct sometype< int, V > {};

Now the partial specialization of sometype works. For example:

sometype< int, 7> mytype;

will use the partial specialization above.

> 2) There is no way to specify a generic nontype template parameter. This
> would be nice:
>
> template< nontype > struct something;
> template< > struct something< char * >;
> template< typename T > something< T * >;
> template< typename T > something< T ** >;
>
> so I can write a template that will take an integer, a particular type
> of pointer, a reference, a pointer to a pointer to anything, etc.

Why would a nontype parameter be useful here? A "something" template
with one parameterized type, such as:

template< class T > struct something {};

can be specialized in all the ways shown above:

template< > struct something< char * > {};

template< class T > struct something< T * > {};
template< class T > struct something< T ** > {};

Greg

Howard Gardner

unread,
Sep 7, 2006, 4:34:26 PM9/7/06
to
Greg Herlihy wrote:
> Howard Gardner wrote:
>
>> These are the particular stumbling blocks that hurt the worst, and I run
>> into them fairly often (or, more correctly, I deploy workarounds for
>> them fairly often).
>>
>> 1) If you write:
>>
>> template< typename T, T V > struct sometype;
>>
>> then there's no way to partially specialize it for a particular type. It
>> seems to me that this should work (but of course it doesn't).
>>
>> template< int V > struct sometype< int, V >;
>
> .because the partial specialization of "sometype" does not declare a
> complete type. A full or partial specialization of a class template
> needs to be a complete type in order to instantiate objects with it:
>
> template< int V >
> struct sometype< int, V > {};
>
> Now the partial specialization of sometype works. For example:
>
> sometype< int, 7> mytype;
>
> will use the partial specialization above.

#include <ostream>
#include <cstddef>
using namespace std;

template< typename T, T V >

struct sometype{static const int value = 0;};

template< int V >
struct sometype< int, V > {static const int value = 1;};

int main()
{
cout << sometype< int, 4 >::value << endl;
}

Using comeau, this prints "0". It instantiated the primary template, not
the specialization.

There are ways to work around this, for example:

#include <ostream>
#include <cstddef>
using namespace std;

template< typename T, T V >

struct meta_k{static const T value = V;};

template< int V >
struct int_meta_k : public meta_k< int, V >{};

template< typename T >
struct sometype;

template< int V >
struct sometype< int_meta_k< V > > {static const int value = 1;};

int main()
{
cout << sometype< int_meta_k< 4 > >::value << endl;
}

but that means:

1. "whatever_meta_k" for every nontype that I want to specialize on
2. "sometype" specialization to match the "whatever_meta_k"
3. user using the proper "whatever_meta_k"

1+2 make work for the library implementer, and for the user if he tries
to extend the library to deal with a new nontype.

3 complicates the library interface.

3 also places a defacto constraint on the design of sometype. If the
primary template is not left incomplete, then it becomes very easy for
the user to write a program that compiles but doesn't work right.

This is another workaround:

#include <ostream>
#include <cstddef>
using namespace std;

template< typename T >
struct meta_k
{
template< T V >
struct invoke{static const T value = V;};
};

template< typename T >
struct sometype;

template< int V >
struct sometype< meta_k< int >::invoke< V > >
{static const int value = 1;};

int main()
{
cout << sometype< meta_k< int >::invoke< 4 > >::value << endl;
}

Now only the per-nontype specialization of sometype is needed. That
saves a little work for the library implementer and library users who
extend it.

It still complicates the interface to the library in a different way,
which is arguably worse than the original complication.

The defacto constraint on the design of sometype is still there.

There are probably other workarounds, but I've yet to find one that
doesn't involve complicating the library interface and constraining the
design of sometype.

Both of those workarounds create genuine problems for the *user* of the
library.

>
>> 2) There is no way to specify a generic nontype template parameter. This
>> would be nice:
>>
>> template< nontype > struct something;
>> template< > struct something< char * >;
>> template< typename T > something< T * >;
>> template< typename T > something< T ** >;
>>
>> so I can write a template that will take an integer, a particular type
>> of pointer, a reference, a pointer to a pointer to anything, etc.
>
> Why would a nontype parameter be useful here? A "something" template
> with one parameterized type, such as:
>
> template< class T > struct something {};
>
> can be specialized in all the ways shown above:
>
> template< > struct something< char * > {};
> template< class T > struct something< T * > {};
> template< class T > struct something< T ** > {};

Yeah, botched. I'll try again :o

It seems that it is impossible to properly implement is_template in the
current language, and it doesn't seem that anyone is proposing to make
it possible. (If I'm wrong on either of those scores, I would embrace
enlightenment.)

The facility is useful in its own right, but changing the language so
that it can be implemented is even more useful. The problems that
prevent a good implementation of is_template prevent the good
implementations of many other facilities too.

Here is a partial implementation of "is_template". It's the best that I
have been able to devise.

#include <ostream>
#include <cstddef>
using namespace std;

template< typename >
struct is_template
{static const bool value = false;};

template< template< typename > class X, typename T >
struct is_template< X< T > >
{static const bool value = true;};

template< template< int > class X, int V >
struct is_template< X< V > >
{static const bool value = true;};

template
<
template< template< typename > class > class X0,
template< typename > class X1
>
struct is_template< X0< X1 > >
{static const bool value = true;};

template< typename >
struct type_template;

template< int >
struct int_template;

template< template< typename > class >
struct type_template_template;

template< bool >
struct uncovered_template;

int main()
{
cout << is_template< type_template< int > >::value << endl;
cout << is_template< int_template< 20 > >::value << endl;
cout
<< is_template< type_template_template< type_template > >::value
<< endl;
cout << is_template< uncovered_template< false > >::value << endl;
}

This prints 1 1 1 0.

The 0 at the end is a real tragedy: the program has compiled, but it
doesn't work. That happened because there are two conflicting
constraints on is_template:

1. The logic of the design demands that the primary template is complete.

2. The structure of the implementation demands that the primary template
is not complete.

Unless there is a radically different approach available (a different
design or a different implementation), then it seems that the choice is
either to abandon the facility altogether or accept the consequences of
an implementation that compiles but lies.

This would be a much better compromise implementation:

template< typename >
struct is_template
{static const bool value = false;};

template< template< typename > class X, typename T >
struct is_template< X< T > >
{static const bool value = true;};

template< template< nontype > class X, nontype V >
struct is_template< X< V > >
{static const bool value = true;};

template
<
template< template< typename... > class > class X,
typename... T
>
struct is_template< X< T > >
{static const bool value = true;};

template
<
template< template< nontype... > class > class X,
nontype... V
>
struct is_template< X< V > >
{static const bool value = true;};

I haven't detected any nontype proposal, and I'm not at all sure that
the variadic template proposal is supposed to allow this.

Even if it works as imagined, the implementation would still be flawed
in two ways: there are an infinite number of permutations of the
template template versions, and only templates with a template parameter
arity of one are detected. It would be feasible to create a practical
implementation of is_template that relied on PP macros to cover a
reasonable set of template template parameters and a generous number of
template parameter arities.

This could cover help cover the issue with template template parameters:

template< typename >
struct is_template
{static const bool value = false;};

template< template< auto > class X, auto A >
struct is_template< X< A > >
{static const bool value = true;};

How thorough that is depends on what auto can match. To be perfect, it
needs to match "any sequence of types, nontypes, and templates."

The variadic template proposal has the same issue:

typename... (any number of types)
nontype... (any number of nontypes)
template< typename > class... (any number of templates accepting one type)
template< typename... > class... (any number of templates accepting any
number of types)

if auto means "typename or nontype", then:

template< auto >... (any number of templates accepting a type or a nontype)

template< auto... > class... (any number of templates accepting any
combination of types or nontypes)

If auto means "typename or nontype or template accepting anything at
all", then you can implement is_template perfectly. If you really meant
"any combination of types, nontypes, and templates accepting one type"
then you'd have to resort to where clauses (ie, you'd have to write a
where clause to pick only the templates that you meant to allow).

If that can be worked out, it's probably reasonable to just hand write
one case for each template parameter arity to be supported.

This could make that unnecessary:

template< typename >
struct is_template< typename >
{static const bool value = false;};

template< template< auto... > class X, auto A... >
struct is_template< X< A > >
{static const bool value = true;};

This is a big set of changes, working out a proper proposal would be a
lot of work, and implementing them would be even more work.

There are three proposals that address major issues in generic
programming: typeof (was this accepted?), concepts, and variadic
templates. Are there any that address the things that "nontype"
addresses, or that "auto" addresses?

Greg Herlihy

unread,
Sep 8, 2006, 2:05:27 AM9/8/06
to

.which is wrong. A matching specialization is always considered a
better match than the general class template. And given the fact that
there are no other specializations of "sometype" from which to choose,
there can be no doubt that the value of sometype<int, 4>::value has to
be "1".

I would suggest reporting this bug to the compiler maker. You may also
wish to consider switching to another C++ compiler that does not have
this defect. I can confirm, for example, that gcc 4.01 selects the
sometype specialization correctly.

Greg

Howard Gardner

unread,
Sep 8, 2006, 12:16:03 PM9/8/06
to
Greg Herlihy wrote:
>> Using comeau, this prints "0". It instantiated the primary template, not
>> the specialization.
>
> .which is wrong. A matching specialization is always considered a
> better match than the general class template. And given the fact that
> there are no other specializations of "sometype" from which to choose,
> there can be no doubt that the value of sometype<int, 4>::value has to
> be "1".

You're right, it should have worked.

> I would suggest reporting this bug to the compiler maker.

It's fixed in 4.3.8 ALPHA.

skaller

unread,
Oct 8, 2006, 4:33:10 PM10/8/06
to
On Sat, 02 Sep 2006 18:17:24 -0600, Douglas Gregor wrote:

> Variadic templates are templates that can take an arbitrary number of
> "extra" template arguments.

[]

> We intend to bring variadic templates to the C++ committee meeting in
> Portland and propose their inclusion in C++0x. Before that, however, we
> would like some feedback from the C++ community.

Here is the typesafe printf from the paper .. done with
standard g++ 4.0.

////////////////////////////////////////////////
#include <iostream>
#include <stdexcept>

template <typename T1, typename T2>
struct Cons {
T1 head;
T2 tail;
Cons (T1 v1, T2 v2) : head(v1), tail(v2) {}
};

template <typename T1, typename T2>
Cons<T1, T2> cons(T1 head, T2 tail) {
return Cons<T1, T2>(head, tail);
}

class Eol {};

template<typename> struct Printf;

void printf(const char* s, Eol dummy) {
while (*s) {
if (*s == '%' && *++s != '%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout << *s++;
}
}

template<typename T1, typename T2>
void printf(const char* s, Cons<T1,T2> list) {
while (*s) {
if (*s == '%' && *++s != '%') {
std::cout << list.head;
printf(++s, list.tail);
}
std::cout << *s++;
}
throw std::runtime_error("extra arguments provided to printf");
}

int main()
{
printf("%x %x %x\n", cons(1, cons(2L, cons (3.4f, Eol() ) ) ) );
return 0;
}
////////////////////////////////////////////////


The Cons construction seems O(N) on construction and
pattern matching and is not limited by an artificial
maximum, and is therefore a better tuple type than
the Boost tuple template.

The above technique is fully general: it works in
any argument position, not just the end, and it supports
trees as well as lists .. and all other inductive
type functors.

Given tuples and tuple fold, the iterated construction:

cons(1, cons(2L, cons (3.4f, Eol() ) ) )

can be simplified syntactically something like:

fold_right(cons, make_tuple(1,2L,3.4f), Eol())

This looks better to me: best to follow standard practice
in function programming languages .. since templates are
already functional.

What's missing is simply syntactic sugar to build
heterogenous lists more conveniently: C++ templates
*already* have the technology to use them.


--
John Skaller <skaller at users dot sf dot net>
Try Felix, the successor to C++ http://felix.sf.net

Andrei Alexandrescu (See Website For Email)

unread,
Oct 9, 2006, 12:50:07 PM10/9/06
to
skaller wrote:
> Here is the typesafe printf from the paper .. done with
> standard g++ 4.0.
[code snipped]

Similar code has popped in a number of places, and is useful as a
baseline that the proposal could compare against.

> The Cons construction seems O(N) on construction and
> pattern matching and is not limited by an artificial
> maximum, and is therefore a better tuple type than
> the Boost tuple template.

It's quite similar to the tuples as described in Modern C++ Design
(which work on typelists and as such aren't intrinsically limited in
size). But the Boost tuple template is eminently more usable. It just
turns out most tuples don't have too many elements.

> The above technique is fully general: it works in
> any argument position, not just the end, and it supports
> trees as well as lists .. and all other inductive
> type functors.

I disagree. When talking about generality, you need to address problems
such as forwarding of functions or multi-argument functor
implementation, which the technique does not solve.

> Given tuples and tuple fold, the iterated construction:
>
> cons(1, cons(2L, cons (3.4f, Eol() ) ) )
>
> can be simplified syntactically something like:
>
> fold_right(cons, make_tuple(1,2L,3.4f), Eol())
>
> This looks better to me: best to follow standard practice
> in function programming languages .. since templates are
> already functional.
>
> What's missing is simply syntactic sugar to build
> heterogenous lists more conveniently: C++ templates
> *already* have the technology to use them.

The syntactic sugar is already there in the guise of operators. You can
form tuples and avoid nested parens with %, (), etc.:

printf("%x %x %x\n", cons(1)(2L)(3.4f));

Again, the technique is a baseline. Claiming that it does all or most of
what variadic templates do would be, I think, overselling.


Andrei

skaller

unread,
Oct 9, 2006, 2:23:19 PM10/9/06
to
On Mon, 09 Oct 2006 16:50:07 +0000, Andrei Alexandrescu (See Website For
Email) wrote:

> skaller wrote:
>> Here is the typesafe printf from the paper .. done with
>> standard g++ 4.0.
> [code snipped]
>

> Again, the technique is a baseline. Claiming that it does all or most of
> what variadic templates do would be, I think, overselling.

Can you give one example that can't be done using cons lists?

I can certainly give on that can't be done with variadic templates
that CAN be done with cons lists: the zip function (takes two
lists and returns a list of pairs).

You can't do that with variadic templates because the only
way to represent two lists is as a list of pairs in the
first place .. :)

Here's another one: given two parallel inheritance
hierarchies, zip them into a single tree (that requires
trees not just lists).

--
John Skaller <skaller at users dot sf dot net>
Try Felix, the successor to C++ http://felix.sf.net

tsko...@voliacable.com

unread,
Oct 9, 2006, 5:24:57 PM10/9/06
to

> You can't do that with variadic templates because the only
> way to represent two lists is as a list of pairs in the
> first place .. :)

That's easy:

template<typename... Args1>
struct zip
{
template<typename... Args2>
struct with
{
typedef std::tr1::tuple< std::pair<Args1,Args2>... > type;
};
};


int main()
{
zip<int,float,double>::with<float,int,double>::type tuple(
std::make_pair(1,2.f),
std::make_pair(2.f,0),
std::make_pair(1.,2.)
);

return 0;

Andrei Alexandrescu (See Website For Email)

unread,
Oct 9, 2006, 6:20:15 PM10/9/06
to
skaller wrote:
> On Mon, 09 Oct 2006 16:50:07 +0000, Andrei Alexandrescu (See Website For
> Email) wrote:
>
>> skaller wrote:
>>> Here is the typesafe printf from the paper .. done with
>>> standard g++ 4.0.
>> [code snipped]
>>
>> Again, the technique is a baseline. Claiming that it does all or most of
>> what variadic templates do would be, I think, overselling.
>
> Can you give one example that can't be done using cons lists?

I already did: implement a function that can forward to any other
function (perhaps doing something extra before and after). Or implement
any pattern using typelists (Command, Factory, Visitor...) as described
in Modern C++ Design. These are important applications IMHO, and open
the door to even more important applications.

> I can certainly give on that can't be done with variadic templates
> that CAN be done with cons lists: the zip function (takes two
> lists and returns a list of pairs).
>
> You can't do that with variadic templates because the only
> way to represent two lists is as a list of pairs in the
> first place .. :)

I think this is mis-formulating the problem. Code using variadics could
also use other amenities, so there is an inclusion relationship. And
variadics would make the code more palatable:

zip(cons(a)(b), cons(c)(d), cons(e)(f));

vs.

zip(cons(cons(a)(b))(cons(c)(d))(cons(e)(f));

> Here's another one: given two parallel inheritance
> hierarchies, zip them into a single tree (that requires
> trees not just lists).

Same comment applies.


Andrei

0 new messages