Topic: [RFC] Variadic templates
Author: skaller@users.sourceforge.net (skaller)
Date: Sun, 8 Oct 2006 20:33:10 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SeeWebsiteForEmail@erdani.org ("Andrei Alexandrescu (See Website For Email)")
Date: Mon, 9 Oct 2006 16:50:07 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: skaller@users.sourceforge.net (skaller)
Date: Mon, 9 Oct 2006 18:23:19 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SeeWebsiteForEmail@erdani.org ("Andrei Alexandrescu (See Website For Email)")
Date: Mon, 9 Oct 2006 22:20:15 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org>
Date: Mon, 4 Sep 2006 17:48:33 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: pmenso57@comcast.net ("Paul Mensonides")
Date: Tue, 5 Sep 2006 07:20:27 GMT Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Niklas Matthies <usenet-nospam@nmhq.net>
Date: Tue, 5 Sep 2006 12:50:05 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org>
Date: Tue, 5 Sep 2006 17:39:45 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Howard Gardner <hgardner@rawbw.com>
Date: Wed, 6 Sep 2006 11:18:05 CST Raw View
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 ;)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Niklas Matthies <usenet-nospam@nmhq.net>
Date: Wed, 6 Sep 2006 11:56:46 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org>
Date: Wed, 6 Sep 2006 18:36:48 CST Raw View
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
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Sat, 2 Sep 2006 18:17:24 CST Raw View
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.gregor@gmail.com
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]