Topic: Omitting default parameters in template templates


Author: sbnaran@uiuc.edu (Siemel B. Naran)
Date: 1999/08/04
Raw View
On 3 Aug 1999 19:27:47 GMT, Andrei Alexandrescu <andrewalex@hotmail.com> wrote:

>However, I think the example you've given is not very illustrative as
>it's an alternative way of saying this:

Moreover, {Scott Meyer}'s example only works on a container that uses the
default argument.  It was
   template <class T, template <class> class Container>
   void doSomething(const Container<T>&);
By my guess, this implies
   void doSomething(const Container<T,default2,default3,default4,...>&);
For Container==std::vector, default2==std::allocator<T>, and the other
defaults are not used.  Which means that the following can't work:
   std::vector<int,myallocator<int>> v;
   doSomething(v); // error: no function to call

I didn't write the template template parameter code for egcs, but
whoever wrote it agrees with me.  Consider this program:
   template <class T> class allocator { };
   template <class T, class Alloc=allocator<T> > class vector { };
   template <class T, template <class> class Container>
   void doSomething(const Container<T>&) { } // LINE 4
   template <class T> class MyAllocator { };
   int main() {
      doSomething(vector<int>()); // LINE 7
      doSomething(vector<int,MyAllocator<int> >()); // LINE 8
   }
Here's the compile error messages.  Note: no error for LINE 7.
   t.cc: In function `int main()':
   t.cc:8:
      conversion from `vector<int,MyAllocator<int> >'
      to non-scalar type `vector<int,allocator<int> >' requested
   t.cc:4:
      in passing argument 1 of
      `doSomething<
         int,
         template <class T, class Alloc = class allocator<T>> vector<T,Alloc>
                  >
            (const vector<int,allocator<int> > &)'


So the template template function doSomething(...) is way too
restrictive.  Interestingly, using fewer template parameters increases
generality:

>template <class Container>
>void printAll(const Container& cont)
>{
>    typedef typename Container::const_iterator iterator;



>So far I've found two good uses for template template parameters:
>
>1. Avoiding repeating the obvious. Say you have a Windows Manager and
>you want to customize the structure it keeps windows in:
>
>class Window { ... };
>template <class Container> class WinManager { ... };
>WinManager<std::deque<Window*> > mgr;
>
>The problem is, I shouldn't have to specify Window* there; it's
>redundant and dangerous. I should say:
>
>WinManager<std::deque> mgr;
>
>so I should use template template parms.

Yes.  And why doesn't class std::stack use this idea?  Currently,
the class declaration is
   template <class T, class Container=deque<T>> class stack;
But it should be
   template <class T, template <class,class> class Container=deque> class stack;

However, this precludes the use of a Container class with a third argument.
Consider
   namespace std { // or any other namespace, like 'funky'
      template <class T, class Alloc=allocator<T>, size_t Block=16>
      class vector;
   }
And now
   int main() { std::stack<int,std::vector>(); }
     // error: std::vector or funky::vector takes 3 template args, not 2

Similarly
   template <template <class,class> Container=deque>
   class WinManager;
But what if the user wants to use a Container that takes 3 args or 4
args, as I have done above with a std::vector or funky::vector taking
3 template arguments.



>2. Choosing design tradeoffs. Imagine the WinManager above has some
>inner classes that have to use containment themselves - like the
>container of controls on a form. They can use the template template
>parameter with another template argument. In this case usage of template
>template parms is mandatory, not only a commodity.

OK.  For example, the template parameter is a private class, or a
class in an unnamed namespace in the cpp file.  So then we must take
a template template parameter.

But once again, the number of template arguments of the template
template parameter may be constraining.

Instead of template template parameters, we could use a normal
template class and give this class a rebind-like mechanism.  Consider:
   template <class T, class X, size_t N>
   class myallocator {
      public:
         template <class U> typedef myallocator<U,X,N> rebind;
   };
I've use the extension of template typedefs just for fun.

Now we can write:
   template <class T, class Alloc=std::allocator<T>>
   class mydigital {
      public:
         ...
      private:
         class Node { };
         typedef Alloc::template rebind<Node> Alloc;
   };



3.
I've seen someone use template template parameters to expand a for
loop at compile time.  However, this requires us to know the limits
of the loop at compile time, and in my experience this is usually
not possible.  Besides, the resulting code is a little harder to
read.


4.
To me, the best use of template template parameters is to loops
and functors to deal with lists of types.  For example.  (BTW, guess
whose idea the 'choose' template is :).


   template <bool, class, class> class choose { };
   template <class T, class U> struct choose<true ,T,U> {typedef T choice;};
   template <class T, class U> struct choose<false,T,U> {typedef U choice;};

   struct useless { typedef useless choice; };

   template <class T, class U=useless>
   struct list
   {
      typedef T car;
      typedef U cdr;
   };

   template <template <class> class Predicate, class list>
   struct find
   {
      typedef typename list::car car;
      typedef typename list::cdr cdr;

      typedef typename
         choose<
                Predicate<car>::result,
                car,
                typename find<Predicate,cdr>::choice
               > :: choice
            choice;
   };

   template <template <class> class Predicate>
   struct find<Predicate,useless>
   {
      typedef useless choice;
   };


   ///
   ///
   ///


   #include <typeinfo>
   #include <iostream>

   const int CHAR_BITS=8;

   template <class T>
   struct Eq16 { static const bool result=((sizeof(T)*CHAR_BITS)==16); };

   using myspace::list;
   using myspace::find;

   typedef find<Eq16, list<int, list<short> > > :: choice int16;

   int main()
   {
      cout
         << "name   == " << typeid(int16).name() << '\n'
         << "sizeof == " << sizeof(int16) << "\n\n";
   }


But even here, we could use other means.  For example, we could write
a program that generates the typedefs for us.

--
----------------------------------
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: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/08/02
Raw View
On 02 Aug 99 08:39:28 GMT, Scott Meyers <smeyers@aristeia.com> wrote:

>  template <typename T,
>            template<typename U> class Container>
>  void printAll(const Container<T>& cont)

>  vector<int> v;
>  ...
>  printAll(v);      // should this compile?


This question came up before.  The standard doesn't say anything
about this particular issue, although it seems like a reasonable
thing to do.  However, one can't write this
   vector<int,myspace::allocator<int> > v;
   printAll(v);
So the function printAll is not that useful.

An implementation that adds additional default template
parameters and additional default function arguments can't be
conforming.  Imagine if your implementation had std::vector<>
as follows:
   template <class T, class Alloc=allocator<T>, size_t Block=16>
   class vector;
The above class passes this template function:
   template <class T, template <class,class,size_t> Thing> void f();
You can say "std::vector<int> v; f(v);".  But when you move to a
library implementation that does not have the third template argument,
then the code fails to compile.

In practice, though, additional default template and function
arguments are not likely to be a problem as people are only likely
to write template template parameters that take one argument.

--
----------------------------------
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: Andrei Alexandrescu <andrewalex@hotmail.com>
Date: 1999/08/03
Raw View
In article <MPG.120ed42ef2707da39896c8@news.teleport.com>,
  smeyers@aristeia.com (Scott Meyers) wrote:
> In general, can template code refer to vector<T> when it really means
> vector<T, allocator<T>, etc.>?

It's the second time I look in the Standard for this (including
dumb find on "default") without any results. The closest references to
the issue I could find were 14.8.2, para 18, and a text whose location
I've lost, saying something like this: template template
arguments are looked up like actual template usage in a function at the
same scope as the function under analysis. Sigh. How's this for a
confusing sentence.

If Siemel is right, I think a defect report should be submitted. This is
an important aspect in using template template parameters, and in
implementing the Standard Library.

However, I think the example you've given is not very illustrative as
it's an alternative way of saying this:

template <class Container>
void printAll(const Container& cont)
{
    typedef typename Container::const_iterator iterator;
    for (iterator i(cont.begin()); i != cont.end(); ++i)
        cout << *i << endl;
}

Which of the two is more natural is a matter of taste. The error text is
often a good gauge. If you call printAll(25) you receive in the first
case an error like: "int is not a template class taking one type
parameter", and in the second, "int is not a class defining an iterator
type".

So far I've found two good uses for template template parameters:

1. Avoiding repeating the obvious. Say you have a Windows Manager and
you want to customize the structure it keeps windows in:

class Window { ... };
template <class Container> class WinManager { ... };
WinManager<std::deque<Window*> > mgr;

The problem is, I shouldn't have to specify Window* there; it's
redundant and dangerous. I should say:

WinManager<std::deque> mgr;

so I should use template template parms.

2. Choosing design tradeoffs. Imagine the WinManager above has some
inner classes that have to use containment themselves - like the
container of controls on a form. They can use the template template
parameter with another template argument. In this case usage of template
template parms is mandatory, not only a commodity.


Andrei


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.


[ 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: smeyers@aristeia.com (Scott Meyers)
Date: 1999/08/02
Raw View
Suppose I'd like to print the contents of any container.  Here's one way I
might try to do it (which I've basically stolen from Bruce Eckel's
downloadable "Thinking in C++"):

  template <typename T,
            template<typename U> class Container>
  void printAll(const Container<T>& cont)
  {
    for (Container<T>::const_iterator i(cont.begin());
         i != cont.end();
         ++i)
     cout << *i << endl;
  }

  vector<int> v;
  ...
  printAll(v);      // should this compile?

This is a nifty use of template templates, but the type of v isn't
vector<int>, it's vector<int, allocator<int>>.  Furthermore, if an
implementation of the standard library decided to give vector additional
parameters (which, provided they all had default values, would, I believe,
be conforming), the type of v might be
vector<int, allocator<int>, fee, fie, foe, fum>.

So should the above template be instantiated and called as one might hope?
In general, can template code refer to vector<T> when it really means
vector<T, allocator<T>, etc.>?

Thanks,

Scott

--
Scott Meyers, Ph.D.                  smeyers@aristeia.com
Software Development Consultant      http://www.aristeia.com/
Visit http://meyerscd.awl.com/ to demo the Effective C++ CD
---
[ 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              ]