Previous Table of Contents Next


6.5.5.1. Template Refinements

The standards committee didn’t change anything fundamental in the template design. It did, however, make more minor refinements and extensions to templates than to any other part of the language. I suspect that the fundamental reason for that was that I hadn’t personally implemented templates. Consequently, there were more weak spots in the template design and in the description of that design than in any other part of C++. Curiously enough, part of the reason for the changes was the strength of the initial design. Templates proved far more useful than most people had expected, and styles of usage that I had anticipated only in the most general sense became popular. The result was a persistent demand for generality and flexibility beyond the ARM specification, which I had made somewhat restrictive in the hope of minimizing implementation problems.

The two most important extensions to the template concept were partial specialization and member templates. The most difficult template issue addressed by the committee was whether it would be a good idea to require that a template be defined in every translation unit in which it was used.

In addition, the overload resolution rules were revised to bring template functions into line with ordinary (non-template) functions. This revision closely mirrors the revision of the overload resolution rules for ordinary functions that was done in connection with Releases 2.0 and 2.1 of Cfront. In both cases, it took serious use of the initial facilities to determine the right rules.

6.5.5.2. Specialization

Specialization was provided in the ARM to allow a separate implementation of a template for a given template argument:

   template<class T> void sort(vector<T>);  // sort vectors using T’s <
                                            // operator

   void sort<char*>(vector<char*>);         // sort vectors using strcmp()

   void f(vector<int>& vi, vector<char*>& vcs)
{
       sort(vi);     // sort using integer <
       sort(vcs);    // sort using strcmp()
   }

The primary use of specialization is to provide separate implementations for “irregular types” such as C-style strings and arrays. I noticed that a generalization of this idea would be

   template<class T> class vector { /* ... */ };     // vector of
                                                     // arbitrary T

   template<class T> class vector<T*> { /* ... */ }; // vector of pointer
                                                     // to T

   vector<complex> vc;        // implement using general template

   vector<Shape*> vps;        // implement using pointer specialization
   vector<Record*> vpr;       // implement using pointer specialization

The importance of specialization is that it provides great flexibility in an orderly and declarative manner without complicating the interface presented to users. Thus, the writer of a template can provide significant optimizations without the intervention of users of the template. For example, all vectors of pointers can be implemented without replication of the code for vector operations. Often, specialization allows us to simplify the interface to a concept by providing separate implementations for types that would otherwise have forced us to make an irregularity visible to users. The sort() template is an example of that. Without the specialization, the comparison criteria would have had to be passed as an argument.

6.5.5.3. Member Templates

By default, you would expect to be able to declare a template as a member of a class. However, while writing the ARM, I couldn’t prove to myself that member templates wouldn’t cause serious implementation problems so I didn’t allow them. This caution proved warranted. Consider this promising idea for a more elegant variant of double dispatch:

   class Shape {
       // ...
       template<class T> virtual Bool intersect(const T&) const =0;
   };

   class Rectangle : public Shape {
       // ...
       template<class T> virtual Bool intersect(const T& s) const;
   };

This would generate a new intersect function each time a user used intersect() with a new argument type. From a user’s point of view, this is an elegant solution to a hard problem. Unfortunately, it breaks C++ object model by requiring the virtual function table to be arbitrarily extended based on use. This would imply postponing calculating the layout of the virtual function table until link time or—in the presence of dynamic linking—until runtime. By implication, the traditional ways of implementing virtual function calls could not be used.

Consequently, only nonvirtual member templates are allowed. The particular problem that prompted the acceptance of member templates was the need to provide conversions between templates that represent “smart pointers”:

   template<class T> class Ptr { // pointer to T
       T* p;
   public:
       Ptr(T*);
       template<class T2> operator Ptr<T2> ()
       {
           return Ptr<T2>(p);    // works if p can be converted to a T2*
       }
       // ...
   };

   void f(Ptr<Shape*> ps, Ptr<Circle*> pc,    Ptr<Record*> pr)
   {
       ps = pc;    // ok a Circle* can be converted to a Shape*
       pc = ps;    // error: a Shape* cannot be converted to a Circle*
       pc = pr;    // error: a Record* cannot be converted to a Circle*
   }

The use of member templates to address the need for smart pointer conversions was first discovered when David Jordan, Andrew Koenig, and I were trying to find a way to specify pointers for the Object Data Management Group’s effort to standardize a C++ interface to object-oriented databases.


Previous Table of Contents Next