A class—either an ordinary class or a class template—may have a member function that is itself a template. Such members are referred to as member templates. Member templates may not be virtual.
As an example of an ordinary class that has a member template, we’ll define a class that is similar to the default deleter type used by unique_ptr
(§ 12.1.5, p. 471). Like the default deleter, our class will have an overloaded function-call operator (§ 14.8, p. 571) that will take a pointer and execute delete
on the given pointer. Unlike the default deleter, our class will also print a message whenever the deleter is executed. Because we want to use our deleter with any type, we’ll make the call operator a template:
// function-object class that calls delete on a given pointer
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr): os(s) { }
// as with any function template, the type of T is deduced by the compiler
template <typename T> void operator()(T *p) const
{ os << "deleting unique_ptr" << std::endl; delete p; }
private:
std::ostream &os;
};
Like any other template, a member template starts with its own template parameter list. Each DebugDelete
object has an ostream
member on which to write, and a member function that is itself a template. We can use this class as a replacement for delete
:
double* p = new double;
DebugDelete d; // an object that can act like a delete expression
d(p); // calls DebugDelete::operator()(double*), which deletes p
int* ip = new int;
// calls operator()(int*) on a temporary DebugDelete object
DebugDelete()(ip);
Because calling a DebugDelete
object delete
s its given pointer, we can also use DebugDelete
as the deleter of a unique_ptr
. To override the deleter of a unique_ptr
, we supply the type of the deleter inside brackets and supply an object of the deleter type to the constructor (§ 12.1.5, p. 471):
// destroying the the object to which p points
// instantiates DebugDelete::operator()<int>(int *)
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// destroying the the object to which sp points
// instantiates DebugDelete::operator()<string>(string*)
unique_ptr<string, DebugDelete> sp(new string, DebugDelete());
Here, we’ve said that p
’s deleter will have type DebugDelete
, and we have supplied an unnamed object of that type in p
’s constructor.
The unique_ptr
destructor calls the DebugDelete
’s call operator. Thus, whenever unique_ptr
’s destructor is instantiated, DebugDelete
’s call operator will also be instantiated: Thus, the definitions above will instantiate:
// sample instantiations for member templates of DebugDelete
void DebugDelete::operator()(int *p) const { delete p; }
void DebugDelete::operator()(string *p) const { delete p; }
We can also define a member template of a class template. In this case, both the class and the member have their own, independent, template parameters.
As an example, we’ll give our Blob
class a constructor that will take two iterators denoting a range of elements to copy. Because we’d like to support iterators into varying kinds of sequences, we’ll make this constructor a template:
template <typename T> class Blob {
template <typename It> Blob(It b, It e);
// ...
};
This constructor has its own template type parameter, It
, which it uses for the type of its two function parameters.
Unlike ordinary function members of class templates, member templates are function templates. When we define a member template outside the body of a class template, we must provide the template parameter list for the class template and for the function template. The parameter list for the class template comes first, followed by the member’s own template parameter list:
template <typename T> // type parameter for the class
template <typename It> // type parameter for the constructor
Blob<T>::Blob(It b, It e):
data(std::make_shared<std::vector<T>>(b, e)) { }
Here we are defining a member of a class template that has one template type parameter, which we have named T
. The member itself is a function template that has a type parameter named It
.
To instantiate a member template of a class template, we must supply arguments for the template parameters for both the class and the function templates. As usual, argument(s) for the class template parameter(s) are determined by the type of the object through which we call the member template. Also as usual, the compiler typically deduces template argument(s) for the member template’s own parameter(s) from the arguments passed in the call (§ 16.1.1, p. 653):
int ia[] = {0,1,2,3,4,5,6,7,8,9};
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"};
// instantiates the Blob<int> class
// and the Blob<int> constructor that has two int* parameters
Blob<int> a1(begin(ia), end(ia));
// instantiates the Blob<int> constructor that has
// two vector<long>::iterator parameters
Blob<int> a2(vi.begin(), vi.end());
// instantiates the Blob<string> class and the Blob<string>
// constructor that has two (list<const char*>::iterator parameters
Blob<string> a3(w.begin(), w.end());
When we define a1
, we explicitly specify that the compiler should instantiate a version of Blob
with the template parameter bound to int
. The type parameter for the constructor’s own parameters will be deduced from the type of begin(ia)
and end(ia)
. That type is int*
. Thus, the definition of a1
instantiates:
Blob<int>::Blob(int*, int*);
The definition of a2
uses the already instantiated Blob<int>
class, and instantiates the constructor with It
replaced by vector<short>::iterator
. The definition of a3
(explicitly) instantiates the Blob
with its template parameter bound to string
and (implicitly) instantiates the member template constructor of that class with its parameter bound to list<const char*>
.
Exercise 16.21: Write your own version of DebugDelete
.
Exercise 16.22: Revise your TextQuery
programs from § 12.3 (p. 484) so that the shared_ptr
members use a DebugDelete
as their deleter (§ 12.1.4, p. 468).
Exercise 16.23: Predict when the call operator will be executed in your main query program. If your expectations and what happens differ, be sure you understand why.
Exercise 16.24: Add a constructor that takes two iterators to your Blob
template.