Выбрать главу

  Seq<T> seq;

If we hadn’t declared Seq to be a template template parameter, the compiler would complain here that Seq is not a template, since we’re using it as such. In main( ) a Container is instantiated to use an Array to hold integers, so Seq stands for Array in this example.

Note that it is not necessary in this case to name the parameter for Seq inside Container’s declaration. The line in question is:

template<class T, template<class> class Seq>

Although we could have written

template<class T, template<class U> class Seq>

the parameter U is not needed anywhere. All that matters is that Seq is a class template that takes a single type parameter. This is analogous to omitting the names of function parameters when they’re not needed, such as when you overload the post-increment operator:.

T operator++(int);

The int here is merely a placeholder and therefore needs no name.

The following program uses a fixed-size array, which has an extra template parameter representing the array dimension:

//: C05:TempTemp2.cpp

// A multi-variate template template parameter

#include <cstddef>

#include <iostream>

using namespace std;

template<class T, size_t N>

class Array {

  T data[N];

  size_t count;

public:

  Array() { count = 0; }

  void push_back(const T& t) {

    if(count < N)

      data[count++] = t;

  }

  void pop_back() {

    if(count > 0)

      --count;

  }

  T* begin() { return data; }

  T* end() { return data + count; }

};

template<class T,size_t N,template<class,size_t> class Seq>

class Container {

  Seq<T,N> seq;

public:

  void append(const T& t) { seq.push_back(t); }

  T* begin() { return seq.begin(); }

  T* end() { return seq.end(); }

};

int main() {

  const size_t N = 10;

  Container<int, N, Array> theData;

  theData.append(1);

  theData.append(2);

  int* p = theData.begin();

  while(p != theData.end())

    cout << *p++ << endl;

} ///:~

Once again, parameter names are not needed in the declaration of Seq inside Container’s declaration, but we need two parameters to declare the data member seq, hence the appearance of the non-type parameter N at the top level.

Combining default arguments with template template parameters is slightly more problematic. The When the compiler looks at the inner parameters of a template template parameter, default arguments are not considered, so you have to repeat the defaults in order to get an exact match. The following example uses a default argument for the fixed-size Array template and shows how to accommodate this quirk in the language.

//: C05:TempTemp3.cpp

//{-bor}

//{-msc}

// Combining template template parameters and

// default arguments

#include <cstddef>

#include <iostream>

using namespace std;

template<class T, size_t N = 10>  // A default argument

class Array {

  T data[N];

  size_t count;

public:

  Array() { count = 0; }

  void push_back(const T& t) {

    if(count < N)

      data[count++] = t;

  }

  void pop_back() {

    if(count > 0)

      --count;

  }

  T* begin() { return data; }

  T* end() { return data + count; }

};

template<class T, template<class, size_t = 10> class Seq>

class Container {

  Seq<T> seq;  // Default used

public:

  void append(const T& t) { seq.push_back(t); }

  T* begin() { return seq.begin(); }

  T* end() { return seq.end(); }

};

int main() {

  Container<int, Array> theData;

  theData.append(1);

  theData.append(2);

  int* p = theData.begin();

  while(p != theData.end())

    cout << *p++ << endl;

} ///:~

It is necessary to include the default dimension of 10 in the line:

template<class T, template<class, size_t = 10> class Seq>

Both the definition of seq in Container and theData in main( ) use the default. The only way to use something other than the default value is as the previous program (TempTemp2.cpp) illustrated. This is the only exception to the rule stated earlier that default arguments should appear only once in a compilation unit.

Since the standard sequence containers (vector, list, and deque, discussed in depth in Chapter 7) have a default allocator argument, the technique shown above is helpful should you ever want to pass one of these sequences as a template parameter. The following program passes a vector and then a list to two instances of Container.

//: C05:TempTemp4.cpp

//{-bor}

//{-msc}

// Passes standard sequences as template arguments

#include <iostream>

#include <list>

#include <memory>  // Declares allocator<T>

#include <vector>

using namespace std;

template<class T, template<class U, class = allocator<U> >

     class Seq>

class Container {

  Seq<T> seq; // Default of allocator<T> applied implicitly

public:

  void push_back(const T& t) { seq.push_back(t); }

  typename Seq<T>::iterator begin() { return seq.begin(); }

  typename Seq<T>::iterator end() { return seq.end(); }

};

int main() {

  // Use a vector

  Container<int, vector> theData;

  theData.push_back(1);

  theData.push_back(2);

  for(vector<int>::iterator p = theData.begin();

      p != theData.end(); ++p) {

    cout << *p << endl;

  }

  // Use a list

  Container<int, list> theOtherData;

  theOtherData.push_back(3);

  theOtherData.push_back(4);

  for(list<int>::iterator p2 = theOtherData.begin();

      p2 != theOtherData.end(); ++p2) {

    cout << *p2 << endl;

  }

} ///:~

In this case we name the first parameter of the inner template Seq (with the name U), because the allocators in the standard sequences must themselves be parameterized with the same type as the contained objects in the sequence. Also, since the default allocator parameter is known, we can omit it in the subsequent references to Seq<T>, as we did in the previous program. To fully explain this example, however, we have to discuss the semantics of the typename keyword.