Topic: Variadic Templates (was: Typesafe variadic functions)


Author: "Ross Smith" <ross.s@ihug.co.nz>
Date: 1998/12/01
Raw View
Petter Urkedal wrote in message <3662C896.27D53F7C@matfys.lth.se>...
>
>Siemel Naran wrote:
>> Actually, we can already do this in the language.  See my example
>> on an earlier thread.  Here's an overview.  The syntax
>>    Arg<int>(1)<<2<<3;
>> creates a list of three integers of type myspace::Arg<int,3>.
>> And myspace::Arg<int,3> has member functions copy and for_each
>> that do one iteration of the copy and for_each and then call
>> myspace::Arg<int,2>::copy or myspace::Arg<int,2>::for_each.
>
>I tried to implement a tensor class using a similar technique.  I put
>it away for two reasons.  Nobody wants to write (slight modification)
>`a[arg << 1 << 2 << 3]' when they mean `a(1,2,3)'.  (If we could overload
>`operator,' for integers, this would be fine -- the corresponding list
>should implicitely convert to the last argument, of course.)

My solution when I wrote a multi-dimensional array class was to have
an associated "dimension" class which had a constructor that took the
first dimension, and an operator() that added a new dimension. The
MD-array has an operator[] that also takes a dimension object, used as
an index.

  MultiArray<double> three_d_array(Dim(10)(20)(30));  // 10x20x30 array
  double element_2_4_6 = three_d_array[Dim(2)(4)(6)]; // get an element

This looks more symmetrical than Siemel's notation, and because the
Dim class is fully inlined, can be optimised to the equivalent of "flat
code".

(For reasons related to the application it was intended for, the
MultiArray class needed to have a variable number of dimensions; it
wasn't appropriate to use the number of dimensions as a template
parameter. The Dim class stored the dimensions/indices in an internal
vector<size_t>.)

--
Ross Smith ................................... mailto:ross.s@ihug.co.nz
.............. The Internet Group, Auckland, New Zealand ..............
                               * * * * *
    To err is human. To really screw up requires the root password.




[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1998/12/01
Raw View
On 1 Dec 1998 21:25:47 GMT, Ross Smith <ross.s@ihug.co.nz> wrote:

>  MultiArray<double> three_d_array(Dim(10)(20)(30));  // 10x20x30 array
>  double element_2_4_6 = three_d_array[Dim(2)(4)(6)]; // get an element
>
>This looks more symmetrical than Siemel's notation, and because the
>Dim class is fully inlined, can be optimised to the equivalent of "flat
>code".

Good.  Another solution for matrices whose number number of dimensions
is known at compile time is to write a program that generates the
template class definition.  Here's what a transcript of the program
might look like:

Enter number of dimensions: 3
Enter a name for the class: Matrix

Output:

template <class T>
class Matrix<T,3>
{
     public:
          typedef size_t size_type;
          typedef T value_type;

          Matrix(size_type,size_type,size_type);
          Matrix(const Matrix&);
          value_type operator()(size_type,size_type,size_type) const;
          ...
};


--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Petter Urkedal <petter@matfys.lth.se>
Date: 1998/11/30
Raw View

Siemel Naran wrote:
> Actually, we can already do this in the language.  See my example
> on an earlier thread.  Here's an overview.  The syntax
>    Arg<int>(1)<<2<<3;
> creates a list of three integers of type myspace::Arg<int,3>.
> And myspace::Arg<int,3> has member functions copy and for_each
> that do one iteration of the copy and for_each and then call
> myspace::Arg<int,2>::copy or myspace::Arg<int,2>::for_each.
>
> This approach works even in heterogenous lists, although it
> requires a little more work.  Our current declaration for class
> Arg is:
>    template <typename T, int N> class Arg;
> We will have to change this to
>    template <typename First, class Rest> class Arg;
>
> So
>    Arg(1)<<2.0<<3;
> generates a list of 3 elements where the first is an int, the second
> is a double, and the third is an int.  BTW, this seems a safe
> alternative to va_lists.

I tried to implement a tensor class using a similar technique.  I put
it away for two reasons.  Nobody wants to write (slight modification)
`a[arg << 1 << 2 << 3]' when they mean `a(1,2,3)'.  (If we could
overload
`operator,' for integers, this would be fine -- the corresponding list
should implicitely convert to the last argument, of course.)

However, the worst part was the loss of efficiency, which is due to the
construction of an additional object.  The
  `T operator()(int i1) {...}
   T operator()(int i1, int i2) {...}
   T operator()(int i1, int i2, int i3) {...} ...'-meditation is simply
much easier for the compiler to optimize, and so would variadic
templates be, since they would be equivalent to this.  The reason is
simply that the latter is a `flat' definition.

An alternative to the extending C++ whould be to create a preprocessor
to carry out the duplication-ceremony (up to a command-line specified
number of template arguments).  Though, I have not come up with a simple
way to implement it whithout depth-parsing of the language.  However,
the mere existence of such a solution suggest that the extension
can be implemented in a `simple-minded' way in actual compilers, and
that
the resulting code is equally optimal as present implementations like
Todd Veldhuizen's Blitz++.

(Maybe the problem does belong to the preprocessor level...  There is
anyway better resons to come up with a new preprocessor which
understands more of the C++ syntax, especially namespaces.  At least
this view makes it clear what the semantic of variadic templates is.)

> I thought about this.  The problem is that overloaded classes are not
> allowed in C++.  So this is an error:
>    tuple<int,int> t;
>    tuple<char,short,int,long> t2;
> because class tuple is overloaded.

Yeah, I know.  I was just going to suggest overloading of template
classes as well :-).  A simple overloading on `typename' and `int'
should be good enough.  (In my opinion we don't really need to
distinguish short, int, long, etc. in template arguments.)

A simple workaround is to define

   template<int I> struct typeint {};

And pass it as

   tuple< double, typeint<3> >

(An alternative to overloading of class-template parameters, is to
allow the compiler to implicitely convert any non-type to some fixed
type `typeint' whenever needed.)

> Anyway, we can use pair<pair<pair<char,short>,int>,long>.
>
> What would be really nice for C++ would be a facility for the
> compiler to automatically generates typedefs from a variable
> template arg list.  For example:
>
>    // fancy definition of typedef class tuple in terms of pair
>    tuple<char,short,int,long> t;
>
> is entirely equivalent to
>
>    pair<pair<pair<char,short>,int>,long> t;
>

I have a solution to this on my web-page -- se the class `list_' on

    http://matfys.lth.se/~petter/src/more/meta/index.html

which is used as

    list_<char, short, int, long>::eval list_object;

(Though, it evaluates to

    typepair< char, typepair< short,
        typepair<int, typepair< long, nulltype> > >

which does not contain any non-type members.)  And, of course, it is
a parameter-duplication-meditation.

We could of course define some tuple-class once and for all, and
use this as a template parameter to our classes whenever we need
variable number of template paramters.  However, the syntax is still
ugly, and it does not solve the optimalization-problem.

> What might the definition of this typedef class look like?  Maybe
> this:
>
> template <...>
> typedef class tuple pair<template 1, tuple<template 2> >;
>
> [...]
>
> [...]  To make things cleaner, and also to make
> clear the connection with existing C++ syntax, here's the new
> and final syntax:
>
> template <...>
> typedef class tuple pair<template[0], tuple<&template[1]> >;
>
> template <...> // partial specialization
> typedef class tuple[2] tuple pair<template[0], template[1]>;

This is an alternative.  But we are now speaking of another
language extension.  So, let's forget for a moment that many people
will be against both yours and my proposal.  My main argument is
that the variadic class defintions is closer to what I've seen
people do (Blitz++), and looks more intuitive to me.

Also, variadic class definitions are allowed to be `flat', they
can be easier for the compiler to optimize.

BTW, the following definition is similar to yours:

    template<typename T, typename... Tail>
    struct tuple : pair< T, tuple<Tail> > {};

    template<typename T> struct tuple<T> : pair< T, void > {};

The only difference is that we are now producing classes which
are derived from pair, rather than pair itself.  This reminds me
of another more probable language extsions.

At present there is no renaming facility for template classes.
So, a more probable extension to C++ then our variadic stuff is
template typedefs:

    temlate<typename T, typename U> typedef pair<T, U> pair_alias;

however, will someone propose class-specialization with typedefs?
This one is more difficult, as it would dissolve the distinction
between classes and types (which I've never understood the importance
of), since we could then say things like

    typedef double result_type<double, double>;

assuming result_type already have a general definition.
And the next step would be `class myint : public int {...}'...

To the point.  With class specializtion by typedefs, the following
tuple-definition would be equivalent to yours:

   template<typename T, typename... Tail>
   typedef pair< T, tuple<Tail> > tuple;  // general template definition

   template<typename T>
   typedef pair< T, void > tuple<T>;      // template specialization

It is shorter.  I think it is also more readable.


> >And finally, the probably most useful cases are multidimintional
> >containers, as tensors and the following `tensin' (TENSor INlined):
>
> What we want here is the ability to define operator() with different
> number of args each time.  So op() for a 2dim matrix takes 2 size_type
> args; op() for a 3dim matrix takes 3 size_type args.  The best we can
> do in the current language is for each op() to take 1 arg that is
> a list like our class myspace::Arg<T,2 or 3>.  Anyway, this should be
> good enough.

True, my example shows the weakness that the number of arguments to
operator() is unspecified in the general-case definition, but since this
recursively reduces to the special-case definition, a call with
incorrect number of arguments will be diagnosed as a call to a non-
existing operator() in the specialized class.

 -petter.

--
[- http://matfys.lth.se/~petter/ -]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Petter Urkedal <petter@matfys.lth.se>
Date: 1998/11/26
Raw View
After seeing code that implements arrays of arbitrary rank (and trying
to wind some myself) I'm convinced that we really need some new
mechanism for variadic parameters.  However, I'm more in favour of
generalizing the template-mechanism with immediate consequence on
both functions and classes.

Thus, I'd like to see function-templates of the following kind

    template<typename OutputIterator, typename T, typename... List>
    inline void write_to_iterator(OutputIterator u, T first, List tail)
{
 *u = first; ++u;
 write_to_iterator(u, tail);
    }

    template<typename OutputIterator, typename T>
    inline void write_to_iterator(OutputIterator u, T last) {
 *u = last; ++u;
    }

That is, `typename...' (or `int...' etc.) declares a template parameter
which is an abstract list of types (or integers etc.);  Abstract in the
sense that it must be resolved at compile-time.  Thus `List' is a
quantity that is meaningful only as an argument to a variadic function.

Above the first definition is the general which uses itself recursively
or the specialization in the second definition.  Since the number of
actual arguments at a particular call is known at compile-time, the
function can be fully inlined recursively.

Of course, we would also use this mechanism on classes.  The following
is a generalization of the STL pair<>:

    template<typename T, typename... Tail>
    struct tuple {
 typedef T first_type;
 typedef tuple<Tail> tail_type;
 first_type first;
 tail_type tail;
 tuple(T first_, Tail tail_) : first(first_), tail(tail_) {}
    };

    template<typename T>
    struct tuple<T> {
 typedef T first_type;
 first_type first;
 tuple(T first_) : first(first_) {}
    };

And finally, the probably most useful cases are multidimintional
containers, as tensors and the following `tensin' (TENSor INlined):


    //  --- i n l i n e d   t e n s o r ---

    template<typename T, int N, int... Tail>
    class tensin {

 tensin<T, Tail> array[N];

    public:
 static const int rank = tensin<T, Tail>::rank + 1;

 template<typename... IndexTail>
   T& operator()(int i, IndexTail itail)
     { return array[i](itail); }

 static int size(int i)
     { return i? tensin<T, Tail>::size(i-1) : N; }
    };


    template<typename T, int N>
    class tensin<T, N> {

 T array[N];

    public:
 static const int rank = 1;

 T& operator()(int i) { return array[i]; }

 static int size(int i) { assert(i == 0); return N; }
    };

In standard C++ this takes repeated duplication of constructors and
functions of all numbers of arguments.

  -petter.


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/11/29
Raw View
On 26 Nov 1998 17:32:27 GMT, Petter Urkedal <petter@matfys.lth.se> wrote:

>Thus, I'd like to see function-templates of the following kind
>
>    template<typename OutputIterator, typename T, typename... List>
>    inline void write_to_iterator(OutputIterator u, T first, List tail)
>{
> *u = first; ++u;
> write_to_iterator(u, tail);
>    }
>
>    template<typename OutputIterator, typename T>
>    inline void write_to_iterator(OutputIterator u, T last) {
> *u = last; ++u;
>    }

Actually, we can already do this in the language.  See my example
on an earlier thread.  Here's an overview.  The syntax
   Arg<int>(1)<<2<<3;
creates a list of three integers of type myspace::Arg<int,3>.
And myspace::Arg<int,3> has member functions copy and for_each
that do one iteration of the copy and for_each and then call
myspace::Arg<int,2>::copy or myspace::Arg<int,2>::for_each.

This approach works even in heterogenous lists, although it
requires a little more work.  Our current declaration for class
Arg is:
   template <typename T, int N> class Arg;
We will have to change this to
   template <typename First, class Rest> class Arg;

So
   Arg(1)<<2.0<<3;
generates a list of 3 elements where the first is an int, the second
is a double, and the third is an int.  BTW, this seems a safe
alternative to va_lists.


>Of course, we would also use this mechanism on classes.  The following
>is a generalization of the STL pair<>:
>
>    template<typename T, typename... Tail>
>    struct tuple {
> typedef T first_type;
> typedef tuple<Tail> tail_type;
> first_type first;
> tail_type tail;
> tuple(T first_, Tail tail_) : first(first_), tail(tail_) {}
>    };
>
>    template<typename T>
>    struct tuple<T> {
> typedef T first_type;
> first_type first;
> tuple(T first_) : first(first_) {}
>    };

I thought about this.  The problem is that overloaded classes are not
allowed in C++.  So this is an error:
   tuple<int,int> t;
   tuple<char,short,int,long> t2;
because class tuple is overloaded.

Anyway, we can use pair<pair<pair<char,short>,int>,long>.

What would be really nice for C++ would be a facility for the
compiler to automatically generates typedefs from a variable
template arg list.  For example:

   // fancy definition of typedef class tuple in terms of pair
   tuple<char,short,int,long> t;

is entirely equivalent to

   pair<pair<pair<char,short>,int>,long> t;


What might the definition of this typedef class look like?  Maybe
this:

template <...>
typedef class tuple pair<template 1, tuple<template 2> >;

"template 1" refers to the first template argument.
"template 2" refers to the rest of the template arguments.
For tuple<char,short,int,long> the steps are

0. running result: pair<...>

1. template 1 is "char"
   template 2 is "<short,int,long>"
   result for this iter: pair<char,tuple<short,int,long>>
   running result      : pair<char,tuple<...>>

2. template 1 is "short"
   template 2 is "<int,long>"
   result for this iter: pair<short,tuple<int,long>>
   running result      : pair<char,pair<short,tuple<...>>>

3. template 1 is "int"
   template 2 is "<long>"
   result for this iter: pair<int,tuple<long>>
   running result      : pair<char,pair<short,pair<int,tuple<...>>>>

4. template 1 is "long"
   template 2 is "<void>"
   result for this iter: pair<int,tuple<void>>
   running result      : pair<char,pair<short,pair<int,pair<long,tuple<void>>>>>


The problem is the end-of-list.  We get this annoying 'void' making its
appearance in there.  So it appears that the solution should be this.
If a third template parameter exists, then the plain old definition
of the typedef class as above is fine:

template <...>
typedef class tuple pair<template 1, tuple<template 2> >;

If a third template parameter does not exist, then we need this

template <...>
typedef class tuple pair<template 1, template 2>;


To achieve this, we will use the following template specialization
syntax:

template <...>
typedef class tuple pair<template 1, tuple<template 2> >;

template <...> // partial specialization
typedef class tuple[2] tuple pair<template 1, template 2>;


However, we've used "template 2" to mean both the second element in
the list as well as the rest of the elements in the list, ie.
second/third/fourth/....  To make things cleaner, and also to make
clear the connection with existing C++ syntax, here's the new
and final syntax:

template <...>
typedef class tuple pair<template[0], tuple<&template[1]> >;

template <...> // partial specialization
typedef class tuple[2] tuple pair<template[0], template[1]>;




>And finally, the probably most useful cases are multidimintional
>containers, as tensors and the following `tensin' (TENSor INlined):

What we want here is the ability to define operator() with different
number of args each time.  So op() for a 2dim matrix takes 2 size_type
args; op() for a 3dim matrix takes 3 size_type args.  The best we can
do in the current language is for each op() to take 1 arg that is
a list like our class myspace::Arg<T,2 or 3>.  Anyway, this should be
good enough.


--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]