Chapter 5

Tricky Basics

This chapter covers some further basic aspects of templates that are relevant to the practical use of templates: an additional use of the typename keyword, defining member functions and nested classes as templates, template template parameters, zero initialization, and some details about using string literals as arguments for function templates. These aspects can be tricky at times, but every day-to-day programmer should have heard of them.

5.1 Keyword typename

The keyword typename was introduced during the standardization of C++ to clarify that an identifier inside a template is a type. Consider the following example:

template<typename T>
class MyClass {
  public:
    
    void foo() {
      typename T::SubType* ptr;
    }
};

Here, the second typename is used to clarify that SubType is a type defined within class T. Thus, ptr is a pointer to the type T::SubType.

Without typename, SubType would be assumed to be a nontype member (e.g., a static data member or an enumerator constant). As a result, the expression

T::SubType* ptr

would be a multiplication of the static SubType member of class T with ptr, which is not an error, because for some instantiations of MyClass<> this could be valid code.

In general, typename has to be used whenever a name that depends on a template parameter is a type. This is discussed in detail in Section 13.3.2 on page 228.

One application of typename is the declaration to iterators of standard containers in generic code:

basics/printcoll.hpp

#include <iostream>

// print elements of an STL container
template<typename T>
void printcoll (T const& coll)
{
    typename T::const_iterator pos;  // iterator to iterate over coll
    typename T::const_iterator end(coll.end());  // end position
    for (pos=coll.begin(); pos!=end; ++pos) {
        std::cout << *pos << ’ ’;
    }
    std::cout << ’\n’;
}

In this function template, the call parameter is an standard container of type T. To iterate over all elements of the container, the iterator type of the container is used, which is declared as type const_iterator inside each standard container class:

class stlcontainer {
 public:
  using iterator = …;        // iterator for read/write access
  using const_iterator = …;  // iterator for read access
 …
};

Thus, to access type const_iterator of template type T, you have to qualify it with a leading typename:

typename T::const_iterator pos;

See Section 13.3.2 on page 228 for more details about the need for typename until C++17. Note that C++20 will probably remove the need for typename in many common cases (see Section 17.1 on page 354 for details).

5.2 Zero Initialization

For fundamental types such as int, double, or pointer types, there is no default constructor that initializes them with a useful default value. Instead, any noninitialized local variable has an undefined value:

void foo()
{
  int x;       // x has undefined value
  int* ptr;    // ptr points to anywhere (instead of nowhere)
}

Now if you write templates and want to have variables of a template type initialized by a default value, you have the problem that a simple definition doesn’t do this for built-in types:

template<typename T>
void foo()
{
  T x;        // x has undefined value if T is built-in type
}

For this reason, it is possible to call explicitly a default constructor for built-in types that initializes them with zero (or false for bool or nullptr for pointers). As a consequence, you can ensure proper initialization even for built-in types by writing the following:

template<typename T>
void foo()
{
  T x{};      // x is zero (or false) if T is a built-in type
}

This way of initialization is called value initialization, which means to either call a provided constructor or zero initialize an object. This even works if the constructor is explicit.

Before C++11, the syntax to ensure proper initialization was

T x = T();      // x is zero (or false) if T is a built-in type

Prior to C++17, this mechanism (which is still supported) only worked if the constructor selected for the copy-initialization is not explicit. In C++17, mandatory copy elision avoids that limitation and either syntax can work, but the braced initialized notation can use an initializer-list constructor1 if no default constructor is available.

To ensure that a member of a class template, for which the type is parameterized, gets initialized, you can define a default constructor that uses a braced initializer to initialize the member:

template<typename T>
class MyClass {
  private:
    T x;
  public:
    MyClass() : x{} {  // ensures that x is initialized even for built-in types
    }
    
};

The pre-C++11 syntax

MyClass() : x() {  //ensures that x is initialized even for built-in types
}

also still works.

Since C++11, you can also provide a default initialization for a nonstatic member, so that the following is also possible:

template<typename T>
class MyClass {
  private:
    T x{};            // zero-initialize x unless otherwise specified
    
};

However, note that default arguments cannot use that syntax. For example,

template<typename T>
void foo(T p{}) {      //ERROR
    …
}

Instead, we have to write:

template<typename T>
void foo(T p = T{}) {  //OK (must use T() before C++11)
    …
}

5.3 Using this->

For class templates with base classes that depend on template parameters, using a name x by itself is not always equivalent to this->x, even though a member x is inherited. For example:

template<typename T>
class Base {
  public:
    void bar();
};

template<typename T>
class Derived : Base<T> {
  public:
    void foo() {
        bar(); // calls external bar() or error
    }
};

In this example, for resolving the symbol bar inside foo(), bar() defined in Base is never considered. Therefore, either you have an error, or another bar() (such as a global bar()) is called.

We discuss this issue in Section 13.4.2 on page 237 in detail. For the moment, as a rule of thumb, we recommend that you always qualify any symbol that is declared in a base that is somehow dependent on a template parameter with this-> or Base<T>::.

5.4 Templates for Raw Arrays and String Literals

When passing raw arrays or string literals to templates, some care has to be taken. First, if the template parameters are declared as references, the arguments don’t decay. That is, a passed argument of "hello" has type char const[6]. This can become a problem if raw arrays or string arguments of different length are passed because the types differ. Only when passing the argument by value, the types decay, so that string literals are converted to type char const*. This is discussed in detail in Chapter 7.

Note that you can also provide templates that specifically deal with raw arrays or string literals. For example:

basics/lessarray.hpp

    template<typename T,
    int N,
    int M> bool less (T(&a)[N], T(&b)[M])
    {
        for (int i = 0; i<N && i<M; ++i)
    {
        if (a[i]<b[i]) return true; if (b[i]<a[i]) return false; } return N < M;
    }

Here, when calling

int x[] = {1, 2, 3};
int y[] = {1, 2, 3, 4, 5};
std::cout << less(x,y) << ’\n’;

less<>() is instantiated with T being int, N being 3, and M being 5.

You can also use this template for string literals:

std::cout << less("ab","abc") << ’\n’;

In this case, less<>() is instantiated with T being char const, N being 3 and M being 4.

If you only want to provide a function template for string literals (and other char arrays), you can do this as follows:

basics/lessstring.hpp

template<int N, int M>
bool less (char const(&a)[N], char const(&b)[M])
{
    for (int i = 0; i<N && i<M; ++i) {
        if (a[i]<b[i]) return true;
        if (b[i]<a[i]) return false;
    }
    return N < M;
}

Note that you can and sometimes have to overload or partially specialize for arrays of unknown bounds. The following program illustrates all possible overloads for arrays:

basics/arrays.hpp

#include <iostream>

template<typename T>
struct MyClass;             // primary template

template<typename T, std::size_t SZ>
struct MyClass<T[SZ]>       // partial specialization for arrays of known bounds
{
  static void print() { std::cout << "print() for T[" << SZ << "]\n"; }
};

template<typename T, std::size_t SZ>
struct MyClass<T(&)[SZ]>    // partial spec. for references to arrays of known bounds
{
  static void print() { std::cout << "print() for T(&)[" << SZ << "]\n"; }
};

template<typename T>
struct MyClass<T[]>         // partial specialization for arrays of unknown bounds
{
  static void print() { std::cout << "print() for T[]\n"; }
};

template<typename T>
struct MyClass<T(&)[]>      // partial spec. for references to arrays of unknown bounds
{
  static void print() { std::cout << "print() for T(&)[]\n"; }
};

template<typename T>
struct MyClass<T*>         // partial specialization for pointers
{
  static void print() { std::cout << "print() for T*\n"; }
};

Here, the class template MyClass<> is specialized for various types: arrays of known and unknown bound, references to arrays of known and unknown bounds, and pointers. Each case is different and can occur when using arrays:

basics/arrays.cpp

#include "arrays.hpp"

template<typename T1, typename T2, typename T3>
void foo(int a1[7], int a2[],    // pointers by language rules
         int (&a3)[42],          // reference to array of known bound
         int (&x0)[],            // reference to array of unknown bound
         T1 x1,                  // passing by value decays
         T2& x2, T3&& x3)        // passing by reference
{
  MyClass<decltype(a1)>::print();      // uses MyClass<T*>
  MyClass<decltype(a2)>::print();      // uses MyClass<T*>
  MyClass<decltype(a3)>::print();      // uses MyClass<T(&)[SZ]>
  MyClass<decltype(x0)>::print();      // uses MyClass<T(&)[]>
  MyClass<decltype(x1)>::print();      // uses MyClass<T*>
  MyClass<decltype(x2)>::print();      // uses MyClass<T(&)[]>
  MyClass<decltype(x3)>::print();      // uses MyClass<T(&)[]>
}

int main()
{
  int a[42];
  MyClass<decltype(a)>::print();       // uses MyClass<T[SZ]>

  extern int x[];                      // forward declare array
  MyClass<decltype(x)>::print();       // uses MyClass<T[]>

  foo(a, a, a, x, x, x, x);
}

int x[] = {0, 8, 15};                // define forward-declared array

Note that a call parameter declared as an array (with or without length) by language rules really has a pointer type. Note also that templates for arrays of unknown bounds can be used for an incomplete type such as

extern int i[];

And when this is passed by reference, it becomes a int(&)[], which can also be used as a template parameter.2

See Section 19.3.1 on page 401 for another example using the different array types in generic code.

5.5 Member Templates

Class members can also be templates. This is possible for both nested classes and member functions. The application and advantage of this ability can again be demonstrated with the Stack<> class template. Normally you can assign stacks to each other only when they have the same type, which implies that the elements have the same type. However, you can’t assign a stack with elements of any other type, even if there is an implicit type conversion for the element types defined:

Stack<int> intStack1, intStack2;  // stacks for ints
Stack<float> floatStack;          // stack for floats

intStack1 = intStack2;            // OK: stacks have same type
floatStack = intStack1;           // ERROR: stacks have different types

The default assignment operator requires that both sides of the assignment operator have the same type, which is not the case if stacks have different element types.

By defining an assignment operator as a template, however, you can enable the assignment of stacks with elements for which an appropriate type conversion is defined. To do this you have to declare Stack<> as follows:

basics/stack5decl.hpp

template<typename T>
class Stack {
  private:
    std::deque<T> elems;        // elements

  public:
    void push(T const&);        // push element
    void pop();                 // pop element
    T const& top() const;       // return top element
    bool empty() const {        // return whether the stack is empty
        return elems.empty();
    }

    // assign stack of elements of type T2
    template<typename T2>
    Stack& operator= (Stack<T2> const&);
};

The following two changes have been made:

1. We added a declaration of an assignment operator for stacks of elements of another type T2.

2. The stack now uses a std::deque<> as an internal container for the elements. Again, this is a consequence of the implementation of the new assignment operator.

The implementation of the new assignment operator looks like this:3

basics/stack5assign.hpp

template<typename T>
template<typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
{
    Stack<T2> tmp(op2);              // create a copy of the assigned stack

    elems.clear();                   // remove existing elements
    while (!tmp.empty()) {           // copy all elements
        elems.push_front(tmp.top());
        tmp.pop();
    }
    return *this;
}

First let’s look at the syntax to define a member template. Inside the template with template parameter T, an inner template with template parameter T2 is defined:

template<typename T>
 template<typename T2>

Inside the member function, you may expect simply to access all necessary data for the assigned stack op2. However, this stack has a different type (if you instantiate a class template for two different argument types, you get two different class types), so you are restricted to using the public interface. It follows that the only way to access the elements is by calling top(). However, each element has to become a top element, then. Thus, a copy of op2 must first be made, so that the elements are taken from that copy by calling pop(). Because top() returns the last element pushed onto the stack, we might prefer to use a container that supports the insertion of elements at the other end of the collection. For this reason, we use a std::deque<>, which provides push_front() to put an element on the other side of the collection.

To get access to all the members of op2 you can declare that all other stack instances are friends:

basics/stack6decl.hpp

template<typename T>
class Stack {
  private:
    std::deque<T> elems;       // elements
  public:
    void push(T const&);       // push element
    void pop();                // pop element
    T const& top() const;      // return top element
    bool empty() const {       // return whether the stack is empty
        return elems.empty();
    }
 

    // assign stack of elements of type T2
    template<typename T2>
    Stack& operator= (Stack<T2> const&);
    // to get access to private members of Stack<T2> for any type T2:
    template<typename> friend class Stack;
};

As you can see, because the name of the template parameter is not used, you can omit it:

template<typename> friend class Stack;

Now, the following implementation of the template assignment operator is possible:

basics/stack6assign.hpp

template<typename T>
 template<typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
{
    elems.clear();                        // remove existing elements
    elems.insert(elems.begin(),           // insert at the beginning
                 op2.elems.begin(),       // all elements from op2
                 op2.elems.end());
    return *this;
}

Whatever your implementation is, having this member template, you can now assign a stack of ints to a stack of floats:

Stack<int> intStack;      // stack for ints
Stack<float> floatStack;  // stack for floats

floatStack = intStack;    // OK: stacks have different types,
                          //     but int converts to float

Of course, this assignment does not change the type of the stack and its elements. After the assignment, the elements of the floatStack are still floats and therefore top() still returns a float.

It may appear that this function would disable type checking such that you could assign a stack with elements of any type, but this is not the case. The necessary type checking occurs when the element of the (copy of the) source stack is moved to the destination stack:

elems.push_front(tmp.top());

If, for example, a stack of strings gets assigned to a stack of floats, the compilation of this line results in an error message stating that the string returned by tmp.top() cannot be passed as an argument to elems.push_front() (the message varies depending on the compiler, but this is the gist of what is meant):

Stack<std::string> stringStack;  // stack of strings
Stack<float> floatStack;         // stack of floats

floatStack = stringStack;        // ERROR: std::string doesn’t convert to float

Again, you could change the implementation to parameterize the internal container type:

basics/stack7decl.hpp

template<typename T, typename Cont = std::deque<T>>
class Stack {
  private:
    Cont elems;                // elements

  public:
    void push(T const&);       // push element
    void pop();                // pop element
    T const& top() const;      // return top element
    bool empty() const {       // return whether the stack is empty
        return elems.empty();
    }

    // assign stack of elements of type T2
    template<typename T2, typename Cont2>
    Stack& operator= (Stack<T2,Cont2> const&);
    // to get access to private members of Stack<T2> for any type T2:
    template<typename, typename> friend class Stack;
};


Then the template assignment operator is implemented like this:

basics/stack7assign.hpp

template<typename T, typename Cont>
 template<typename T2, typename Cont2>
Stack<T,Cont>&
Stack<T,Cont>::operator= (Stack<T2,Cont2> const& op2)
{
    elems.clear();                        // remove existing elements
    elems.insert(elems.begin(),           // insert at the beginning
                 op2.elems.begin(),       // all elements from op2
                 op2.elems.end());
    return *this;
}

Remember, for class templates, only those member functions that are called are instantiated. Thus, if you avoid assigning a stack with elements of a different type, you could even use a vector as an internal container:

// stack for ints using a vector as an internal container
Stack<int,std::vector<int>> vStack;

vStack.push(42); vStack.push(7);
std::cout << vStack.top() << ’\n’;

Because the assignment operator template isn’t necessary, no error message of a missing member function push_front() occurs and the program is fine.

For the complete implementation of the last example, see all the files with a name that starts with stack7 in the subdirectory basics.

Specialization of Member Function Templates

Member function templates can also be partially or fully specialized. For example, for the following class:

basics/boolstring.hpp

class BoolString {
  private:
    std::string value;
  public:
    BoolString (std::string const& s)
     : value(s) {
    }

    template<typename T = std::string>
    T get() const {         // get value (converted to T)
      return value;
    }
};

you can provide a full specialization for the member function template as follows:

basics/boolstringgetbool.hpp

// full specialization for BoolString::getValue<>() for bool
template<>
inline bool BoolString::get<bool>() const {
  return value == "true" || value == "1" || value == "on";
}

Note that you don’t need and also can’t declare the specializations; you only define them. Because it is a full specialization and it is in a header file you have to declare it with inline to avoid errors if the definition is included by different translation units.

You can use class and the full specialization as follows:

std::cout << std::boolalpha;
BoolString s1("hello");
std::cout << s1.get() << ’\n’;        //prints hello
std::cout << s1.get<bool>() << ’\n’;  //prints false
BoolString s2("on");
std::cout << s2.get<bool>() << ’\n’;  //prints true

Special Member Function Templates

Template member functions can be used wherever special member functions allow copying or moving objects. Similar to assignment operators as defined above, they can also be constructors. However, note that template constructors or template assignment operators don’t replace predefined constructors or assignment operators. Member templates don’t count as the special member functions that copy or move objects. In this example, for assignments of stacks of the same type, the default assignment operator is still called.

This effect can be good and bad:

• It can happen that a template constructor or assignment operator is a better match than the predefined copy/move constructor or assignment operator, although a template version is provided for initialization of other types only. See Section 6.2 on page 95 for details.

• It is not easy to “templify” a copy/move constructor, for example, to be able to constrain its existence. See Section 6.4 on page 102 for details.

5.5.1 The .template Construct

Sometimes, it is necessary to explicitly qualify template arguments when calling a member template. In that case, you have to use the template keyword to ensure that a < is the beginning of the template argument list. Consider the following example using the standard bitset type:

template<unsigned long N>
void printBitset (std::bitset<N> const& bs) {
  std::cout << bs.template to_string<char, std::char_traits<char>,
                                     std::allocator<char>>();
}

For the bitset bs we call the member function template to_string(), while explicitly specifying the string type details. Without that extra use of .template, the compiler does not know that the less-than token (<) that follows is not really less-than but the beginning of a template argument list. Note that this is a problem only if the construct before the period depends on a template parameter. In our example, the parameter bs depends on the template parameter N.

The .template notation (and similar notations such as ->template and ::template) should be used only inside templates and only if they follow something that depends on a template parameter. See Section 13.3.3 on page 230 for details.

5.5.2 Generic Lambdas and Member Templates

Note that generic lambdas, introduced with C++14, are shortcuts for member templates. A simple lambda computing the “sum” of two arguments of arbitrary types:

[] (auto x, auto y) {
  return x + y;
}

is a shortcut for a default-constructed object of the following class:

class SomeCompilerSpecificName {
  public:
    SomeCompilerSpecificName();  // constructor only callable by compiler
    template<typename T1, typename T2>
    auto operator() (T1 x, T2 y) const {
      return x + y;
    }
};

See Section 15.10.6 on page 309 for details.

5.6 Variable Templates

Since C++14, variables also can be parameterized by a specific type. Such a thing is called a variable template.4

For example, you can use the following code to define the value of ˇ while still not defining the type of the value:

template<typename T>
constexpr T pi{3.1415926535897932385};

Note that, as for all templates, this declaration may not occur inside functions or block scope.

To use a variable template, you have to specify its type. For example, the following code uses two different variables of the scope where pi<> is declared:

std::cout << pi<double> << ’\n’;
std::cout << pi<float> << ’\n’;

You can also declare variable templates that are used in different translation units:

//== header.hpp:
template<typename T> T val{};     // zero initialized value

//== translation unit 1:
#include "header.hpp"

int main()
{
  val<long> = 42;
  print();
}

//== translation unit 2:
#include "header.hpp"

void print()
{
  std::cout << val<long> << ’\n’; // OK: prints 42
}

Variable templates can also have default template arguments:

template<typename T = long double>
constexpr T pi = T{3.1415926535897932385};

You can use the default or any other type:

std::cout << pi<> << ’\n’;       //outputs a long double
std::cout << pi<float> << ’\n’;  //outputs a float

However, note that you always have to specify the angle brackets. Just using pi is an error:

std::cout << pi << ’\n’;        //ERROR

Variable templates can also be parameterized by nontype parameters, which also may be used to parameterize the initializer. For example:

#include <iostream>
#include <array>

template<int N>
  std::array<int,N> arr{};         // array with N elements, zero-initialized
template<auto N>
  constexpr decltype(N) dval = N;  // type of dval depends on passed value

int main()
{
  std::cout << dval<’c’> << ’\n’;             // N has value ’c’ of type char
  arr<10>[0] = 42;                            // sets first element of global arr
  for (std::size_t i=0; i<arr<10>.size(); ++i) {   // uses values set in arr
   std::cout << arr<10>[i] << ’\n’;
  }
}

Again, note that even when the initialization of and iteration over arr happens in different translation units the same variable std::array<int,10> arr of global scope is still used.

Variable Templates for Data Members

A useful application of variable templates is to define variables that represent members of class templates. For example, if a class template is defined as follows:

template<typename T>
class MyClass {
  public:
    static constexpr int max = 1000;
};

which allows you to define different values for different specializations of MyClass<>, then you can define

template<typename T>
int myMax = MyClass<T>::max;

so that application programmers can just write

auto i = myMax<std::string>;

instead of

auto i = MyClass<std::string>::max;

This means, for a standard class such as

namespace std {
  template<typename T> class numeric_limits {
    public:
      …
      static constexpr bool is_signed = false;
      …
  };
}

you can define

template<typename T>
constexpr bool isSigned = std::numeric_limits<T>::is_signed;

to be able to write

isSigned<char>

instead of

std::numeric_limits<char>::is_signed

Type Traits Suffix _v

Since C++17, the standard library uses the technique of variable templates to define shortcuts for all type traits in the standard library that yield a (Boolean) value. For example, to be able to write

std::is_const_v<T>        // since C++17

instead of

std::is_const<T>::value        //since C++11

the standard library defines

namespace std {
  template<typename T> constexpr bool is_const_v = is_const<T>::value;
}

5.7 Template Template Parameters

It can be useful to allow a template parameter itself to be a class template. Again, our stack class template can be used as an example.

To use a different internal container for stacks, the application programmer has to specify the element type twice. Thus, to specify the type of the internal container, you have to pass the type of the container and the type of its elements again:

Stack<int, std::vector<int>> vStack;  // integer stack that uses a vector

Using template template parameters allows you to declare the Stack class template by specifying the type of the container without respecifying the type of its elements:

Stack<int, std::vector> vStack;        // integer stack that uses a vector

To do this, you must specify the second template parameter as a template template parameter. In principle, this looks as follows:5

basics/stack8decl.hpp

template<typename T,
         template<typename Elem> class Cont = std::deque>
class Stack {
  private:
    Cont<T> elems;             // elements

  public:
    void push(T const&);       // push element
    void pop();                // pop element
    T const& top() const;      // return top element
    bool empty() const {       // return whether the stack is empty
        return elems.empty();
    }
    
};

The difference is that the second template parameter is declared as being a class template:

template<typename Elem> class Cont

The default value has changed from std::deque<T> to std::deque. This parameter has to be a class template, which is instantiated for the type that is passed as the first template parameter:

Cont<T> elems;

This use of the first template parameter for the instantiation of the second template parameter is particular to this example. In general, you can instantiate a template template parameter with any type inside a class template.

As usual, instead of typename you could use the keyword class for template parameters. Before C++11, Cont could only be substituted by the name of a class template.

template<typename T,
         template<class Elem> class Cont = std::deque>
class Stack {                                 //OK
  …
};

Since C++11, we can also substitute Cont with the name of an alias template, but it wasn’t until C++17 that a corresponding change was made to permit the use of the keyword typename instead of class to declare a template template parameter:

template<typename T,
         template<typename Elem> typename Cont = std::deque>
class Stack {                                //ERROR before C++17
  …
};

Those two variants mean exactly the same thing: Using class instead of typename does not prevent us from specifying an alias template as the argument corresponding to the Cont parameter.

Because the template parameter of the template template parameter is not used, it is customary to omit its name (unless it provides useful documentation):

template<typename T,
         template<typename> class Cont = std::deque>
class Stack {
  …
};

Member functions must be modified accordingly. Thus, you have to specify the second template parameter as the template template parameter. The same applies to the implementation of the member function. The push() member function, for example, is implemented as follows:

template<typename T, template<typename> class Cont>
void Stack<T,Cont>::push (T const& elem)
{
    elems.push_back(elem);    // append copy of passed elem
}

Note that while template template parameters are placeholders for class or alias templates, there is no corresponding placeholder for function or variable templates.

Template Template Argument Matching

If you try to use the new version of Stack, you may get an error message saying that the default value std::deque is not compatible with the template template parameter Cont. The problem is that prior to C++17 a template template argument had to be a template with parameters that exactly match the parameters of the template template parameter it substitutes, with some exceptions related to variadic template parameters (see Section 12.3.4 on page 197). Default template arguments of template template arguments were not considered, so that a match couldn’t be achieved by leaving out arguments that have default values (in C++17, default arguments are considered).

The pre-C++17 problem in this example is that the std::deque template of the standard library has more than one parameter: The second parameter (which describes an allocator) has a default value, but prior to C++17 this was not considered when matching std::deque to the Cont parameter.

There is a workaround, however. We can rewrite the class declaration so that the Cont parameter expects containers with two template parameters:

template<typename T,
         template<typename Elem,
                  typename Alloc = std::allocator<Elem>>
          class Cont = std::deque>
class Stack {
  private:
    Cont<T> elems;         // elements
    …
};

Again, we could omit Alloc because it is not used.

The final version of our Stack template (including member templates for assignments of stacks of different element types) now looks as follows:

basics/stack9.hpp

#include <deque>
#include <cassert>
#include <memory>

template<typename T,
         template<typename Elem,
                  typename = std::allocator<Elem>>
         class Cont = std::deque>
class Stack {
  private:
    Cont<T> elems;            // elements

  public:
    void push(T const&);      // push element
    void pop();               // pop element
    T const& top() const;     // return top element
    bool empty() const {      // return whether the stack is empty
        return elems.empty();
    }

    // assign stack of elements of type T2
    template<typename T2,
             template<typename Elem2,
                      typename = std::allocator<Elem2>
                     >class Cont2>
    Stack<T,Cont>& operator= (Stack<T2,Cont2> const&);
    // to get access to private members of any Stack with elements of type T2:
    template<typename, template<typename, typename>class>
    friend class Stack;
};

template<typename T, template<typename,typename> class Cont>
void Stack<T,Cont>::push (T const& elem)
{
    elems.push_back(elem);    // append copy of passed elem
}

template<typename T, template<typename,typename> class Cont>
void Stack<T,Cont>::pop ()
{
    assert(!elems.empty());
    elems.pop_back();          // remove last element
}

template<typename T, template<typename,typename> class Cont>
T const& Stack<T,Cont>::top () const
{
    assert(!elems.empty());
    return elems.back();       // return copy of last element
}

template<typename T, template<typename,typename> class Cont>
 template<typename T2, template<typename,typename> class Cont2>
Stack<T,Cont>&
Stack<T,Cont>::operator= (Stack<T2,Cont2> const& op2)
{
    elems.clear();                        // remove existing elements
    elems.insert(elems.begin(),           // insert at the beginning
                 op2.elems.begin(),       // all elements from op2
                 op2.elems.end());
    return *this;
}

Note again that to get access to all the members of op2 we declare that all other stack instances are friends (omitting the names of the template parameters):

template<typename, template<typename, typename>class>
friend class Stack;

Still, not all standard container templates can be used for Cont parameter. For example, std::array will not work because it includes a nontype template parameter for the array length that has no match in our template template parameter declaration.

The following program uses all features of this final version:

basics/stack9test.cpp

#include "stack9.hpp"
#include <iostream>
#include <vector>

int main()
{
  Stack<int>   iStack;    // stack of ints
  Stack<float> fStack;    // stack of floats

// manipulate int stack
iStack.push(1);
iStack.push(2);
std::cout << "iStack.top(): " << iStack.top() << ’\n’;

// manipulate float stack:
fStack.push(3.3);
std::cout << "fStack.top(): " << fStack.top() << ’\n’;

// assign stack of different type and manipulate again
fStack = iStack;
fStack.push(4.4);
std::cout << "fStack.top(): " << fStack.top() << ’\n’;

// stack for doubless using a vector as an internal container
Stack<double, std::vector> vStack;
vStack.push(5.5);
vStack.push(6.6);
std::cout << "vStack.top(): " << vStack.top() << ’\n’;

vStack = fStack;
std::cout << "vStack: ";
while (! vStack.empty()) {
  std::cout << vStack.top() << ’ ’;
  vStack.pop();
}
std::cout << ’\n’;
}

The program has the following output:

iStack.top(): 2
fStack.top(): 3.3
fStack.top(): 4.4
vStack.top(): 6.6
vStack: 4.4 2 1

For further discussion and examples of template template parameters, see Section 12.2.3 on page 187, Section 12.3.4 on page 197, and Section 19.2.2 on page 398.

5.8 Summary

• To access a type name that depends on a template parameter, you have to qualify the name with a leading typename.

• To access members of bases classes that depend on template parameters, you have to qualify the access by this-> or their class name.

• Nested classes and member functions can also be templates. One application is the ability to implement generic operations with internal type conversions.

• Template versions of constructors or assignment operators don’t replace predefined constructors or assignment operators.

• By using braced initialization or explicitly calling a default constructor, you can ensure that variables and members of templates are initialized with a default value even if they are instantiated with a built-in type.

• You can provide specific templates for raw arrays, which can also be applicable to string literals.

• When passing raw arrays or string literals, arguments decay (perform an array-to-pointer conversion) during argument deduction if and only if the parameter is not a reference.

• You can define variable templates (since C++14).

• You can also use class templates as template parameters, as template template parameters.

• Template template arguments must usually match their parameters exactly.

1 That is, a constructor with a parameter of type std::initializer_list<X>, for some type X.

2 Parameters of type X (&)[]—for some arbitrary type X—have become valid only in C++17, through the resolution of Core issue 393. However, many compilers accepted such parameters in earlier versions of the language.

3 This is a basic implementation to demonstrate the template features. Issues like proper exception handling are certainly missing.

4 Yes, we have very similar terms for very different things: A variable template is a variable that is a template (variable is a noun here). A variadic template is a template for a variadic number of template parameters (variadic is an adjective here).

5 Before C++17, there is an issue with this version that we explain in a minute. However, this affects only the default value std::deque. Thus, we can illustrate the general features of template template parameters with this default value before we discuss how to deal with it before C++17.