Team LiB   Previous Section   Next Section

7.4 Class Templates

A class template defines a pattern for any number of classes whose definitions depend on the template parameters. The compiler treats every member function of the class as a function template with the same parameters as the class template.

Class templates are used throughout the standard library for containers (list<>, map<>, etc.), complex numbers (complex<>), and even strings (basic_string<>) and I/O (basic_istream<>, etc.).

The basic form of a class template is a template declaration header followed by a class declaration or definition:

template<typename T>
struct point {
  T x, y;
};

To use the class template, supply an argument for each template parameter (or let the compiler substitute a default argument). Use the template name and arguments the way you would a class name.

point<int> pt = { 42, 10 };
typedef point<double> dpoint;
dpoint dp = { 3.14159, 2.71828 };

In member definitions that are separate from the class definition, you must declare the template using the same template parameter types in the same order, but without any default arguments. (You can change the names of the template parameters, but be sure to avoid name collisions with base classes and their members. See Section 7.8 later in this chapter for more information.) Declare the member definitions the way you would any other definition, except that the class name is a template name (with arguments), and the definition is preceded by a template declaration header.

In the class scope (in the class definition, or in the definition of a class member), the bare class name is shorthand for the full template name with arguments. Thus, you can use the bare identifier as the constructor name, after the tilde in a destructor name, in parameter lists, and so on. Outside the class scope, you must supply template arguments when using the template name. You can also use the template name with different arguments to specify a different template instance when declaring a member template. The following example shows how the template name can be used in different ways:

template<typename T> struct point {
  point(T x, T y);   // Or point<T>(T x, T y);
  ~point<T>(  );     // Or ~point(  );
  template<typename U>
  point(const point<U>& that);
  ...
};

Example 7-5 shows a class template with several different ways to define its member function templates.

Example 7-5. Defining class templates
template<typename T, typename U = int>
class demo {
public:
  demo(T t, U u);
  ~demo(  );
  static T data;
  class inner; // inner is a plain class, not a template.
};

template<typename T, typename U>
demo<T,U>::demo(T, U)     // Or demo<T,U>::demo<T,U>(T, U)
{}

template<typename T, typename U>
demo<T,U>::~demo<T,U>(  ) // Or demo<T,U>::~demo(  )
{}

template<typename U, typename T> // Allowed, but confusing
U demo<U,T>::data;

template<typename T, typename U>
class demo<T,U>::inner {
public:
  inner(T, demo<T,U>& outer);
private:
  demo<T,U>& outer;
};

// The class name is "demo<T,U>::inner", and the constructor name is "inner".
template<typename T, typename U>
demo<T,U>::inner::inner(T, demo<T,U>& outer)
: outer(outer)
{}

The template defines only the pattern; the template must be instantiated to declare or define a class. See the sections Section 7.5 and Section 7.7 later in this chapter for more information.

7.4.1 Member Templates

A member template is a template inside a class template or non-template class that has its own template declaration header, with its own template parameters. It can be a member function or a nested type. Member templates have the following restrictions:

  • Local classes cannot have member templates.

  • A member function template cannot be a destructor.

  • A member function template cannot be virtual.

  • If a base class has a virtual function, a member function template of the same name and parameters in a derived class does not override that function.

Type conversion functions have additional rules because they cannot be instantiated using the normal template instantiation or specialization syntax:

  • A using declaration cannot refer to a type conversion template in a base class.

  • Use ordinary type conversion syntax to instantiate a type conversion template:

    template<typename T>
    struct silly {
      template<typename U> operator U*(  ) { return 0; }
    };
    ...
    silly<int> s, t(42);
    if (static_cast<void*>(s))
      std::cout << "not null\n";

A class can have a non-template member function with the same name as a member function template. The usual overloading rules apply (Chapter 5), which means the compiler usually prefers the non-template function to the function template. If you want to make sure the function template is called, you can explicitly instantiate the template:

template<typename T>
struct demo {
  template<typename U> void func(U);
  void func(int);
};

demo<int> d;
d.func(42);      // Calls func(int)
d.func<int>(42); // Calls func<int>

7.4.2 Friends

A friend (of a class template or an ordinary class) can be a template, a specialization of a template, or a non-template function or class. A friend declaration cannot be a partial specialization. If the friend is a template, all instances of the template are friends. If the class granting friendship is a template, all instances of the template grant friendship. A specialization of a template can be a friend, in which case instances of only that specialization are granted friendship:

template<typename T> class buddy {};
template<typename T> class special {};
class demo {
  // All instances of buddy<> are friends.
  template<typename T> friend class buddy;
  // special<int> is a friend, but not special<char>, etc.
  friend class special<int>;
};

When you use the containing class template as a function parameter, you probably want friend functions to be templates also:

template<typename T>
class outer {
  friend void func1(outer& o);    // Wrong

  template<typename U>
  friend void func2(outer<U>& o); // Right
};

A friend function can be defined in a class template, in which case every instance of the class template defines the function. The function definition is compiled even if it is not used.

You cannot declare a friend template in a local class.

If a friend declaration is a specialization of a function template, you cannot specify any default function arguments, and you cannot use the inline specifier. These items can be used in the original function template declaration, however.

All the rules for function templates apply to friend function templates. (See Section 7.3 earlier in this chapter for details.) Example 7-6 shows examples of friend declarations and templates.

Example 7-6. Declaring friend templates and friends of templates
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <iterator>
#include <ostream>

template<typename T, std::size_t Size>
class array {
public:
  typedef T value_type;

  class iterator_base :
    std::iterator<std::random_access_iterator_tag, T> {
  public:
    typedef T value_type;
    typedef std::size_t size_type;
    typedef std::ptrdiff_t distance_type;

    friend inline bool operator==(const iterator_base& x,
                                  const iterator_base& y)
    {
      return x.ptr_ == y.ptr_;
    }
    friend inline bool operator!=(const iterator_base& x,
                                  const iterator_base& y)
    {
      return ! (x == y);
    }
    friend inline ptrdiff_t operator-(const iterator_base& x,
                                      const iterator_base& y)
    {
      return x.ptr_ - y.ptr_;
    }

  protected:
    iterator_base(const iterator_base& that) 
      : ptr_(that.ptr_) {}
    iterator_base(T* ptr) : ptr_(ptr) {}
    iterator_base(const array& a, std::size_t i)
      : ptr_(a.data_ + i) {}

    T* ptr_;
  };

  friend class iterator_base;

  class iterator : public iterator_base {
  public:
    iterator(const iterator& that) : iterator_base(that) {}
    T& operator*(  )         const { return *this->ptr_; }
    iterator& operator++(  ) { ++this->ptr_; return *this; }
    T* operator->(  )        const { return this->ptr_; }

  private:
    friend class array;
    iterator(const array& a, std::size_t i = 0) 
      : iterator_base(a, i) {}
    iterator(T* ptr) : iterator_base(ptr) {}

    friend inline iterator operator+(iterator iter, int off)
    {
      return iterator(iter.ptr_ + off);
    }
  };

  array(  ) : data_(new T[Size]) {}
  array(const array& that);
  ~array(  )                     { delete[] data_; }
  iterator begin(  )             { return iterator(*this); }
  const_iterator begin(  ) const { return iterator(*this); }
  iterator end(  );
  const_iterator end(  )   const;
  T& operator[](std::size_t i);
  T operator[](std::size_t i);

  template<typename U, std::size_t USize>
  friend void swap(array<U,USize>& a, array<U,USize>& b);

private:
  T* data_;
};

template<typename T, std::size_t Size>
void swap(array<T,Size>& a, array<T,Size>& b)
{
  T* tmp = a.data_;
  a.data_ = b.data_;
  b.data_ = tmp;
}
    Team LiB   Previous Section   Next Section