The primary template and implementation selection

A template defines general behavior for multiple types. For example, the primary Stack<T> template stores its elements in a std::vector:

#include <cassert>
#include <vector>

template<typename T>
class Stack
{
    std::vector<T> elems;

public:
    void push(const T& value)
    {
        elems.push_back(value);
    }

    void pop()
    {
        assert(!elems.empty());
        elems.pop_back();
    }

    const T& top() const
    {
        assert(!elems.empty());
        return elems.back();
    }

    bool empty() const
    {
        return elems.empty();
    }
};

Without any specializations, this template is used for all valid arguments, such as Stack<int> and Stack<double>.

When specializations are available, the compiler first looks for an exact full specialization. If none exists, it considers matching partial specializations and selects the single most specialized one. If no partial specialization matches, the primary template is used.

Full class specialization

A full specialization defines a separate implementation for a specific set of template arguments. The following Stack<std::string> specialization uses std::deque instead of the container used by the primary template:

#include <deque>
#include <string>

template<>
class Stack<std::string>
{
    std::deque<std::string> elems;

public:
    void push(const std::string& value);

    void pop()
    {
        assert(!elems.empty());
        elems.pop_back();
    }

    const std::string& top() const
    {
        assert(!elems.empty());
        return elems.back();
    }

    bool empty() const
    {
        return elems.empty();
    }
};

The empty parameter list in template<> means that there are no new template parameters: the argument has already been fixed as std::string.

A full specialization is an independent class. It does not inherit fields or method definitions from the primary template, so every required part of its interface must be declared again.

A member of a full specialization defined outside the class is written like a member of an ordinary class. The template<> prefix must not be repeated:

inline void Stack<std::string>::push(const std::string& value)
{
    elems.push_back(value);
}

The inline specifier allows this definition to appear in a header included by multiple translation units. If the definition appears in only one .cpp file, inline is usually unnecessary.

Usage

Stack<std::string> strings;
strings.push("hello");

const std::string& value = strings.top();

The detailed rules are covered by the standard sections on explicit specializations and inline functions.

Full specialization of an individual member function

If only one member needs different behavior, the entire class does not have to be specialized. Instead, that member can have a full, or explicit, specialization for a specific argument.

#include <iostream>

template<typename T>
struct Printer
{
    void print(const T& value) const
    {
        std::cout << value;
    }
};

template<>
inline void Printer<bool>::print(const bool& value) const
{
    std::cout << (value ? "true" : "false");
}
Printer<int>{}.print(42);     // 42
Printer<bool>{}.print(true);  // true

Only Printer<bool>::print() is specialized here. The Printer<bool> class itself is still generated from the primary template, so its other members retain their original definitions.

The syntax and restrictions are described in the rules for explicit specialization of templates and their members.

Partial specialization for pointer types

A partial specialization describes a pattern that template arguments must match rather than one specific argument. The Stack<T*> implementation is selected for any pointer type:

template<typename T>
class Stack<T*>
{
    std::vector<T*> elems;

public:
    void push(T* value)
    {
        elems.push_back(value);
    }

    T* pop();

    T* top() const
    {
        assert(!elems.empty());
        return elems.back();
    }

    bool empty() const
    {
        return elems.empty();
    }
};

When the actual argument is matched against the T* pattern, the compiler deduces the parameter T:

Stack<int*> a;        // T = int
Stack<const int*> b;  // T = const int
Stack<int**> c;       // T = int*

When a member is defined outside the class, both the template parameter list and the Stack<T*> specialization pattern are retained:

template<typename T>
T* Stack<T*>::pop()
{
    assert(!elems.empty());

    T* result = elems.back();
    elems.pop_back();

    return result;
}

A stack of non-owning pointers

int value = 42;

Stack<int*> pointers;
pointers.push(&value);

int* p = pointers.pop();
*p = 100; // value is now 100

Removing the address from the container does not destroy the value object. The calling code remains responsible for the lifetime of the referenced objects.

Consequently, pop() returns a pointer in this example even though its return type is void in the primary template. This is valid, but generic code that works with different Stack variants must account for the difference between their interfaces.

For more details, see the rules for matching partial specializations and members of partial specializations.

Specializations with multiple parameters

A partial specialization can account for specific types, argument patterns, and relationships between multiple arguments. Each implementation below has an id that makes the compiler’s selection observable:

// Primary template
template<typename T1, typename T2>
struct MyClass
{
    static constexpr int id = 0;
};

// Identical types
template<typename T>
struct MyClass<T, T>
{
    static constexpr int id = 1;
};

// The second argument is int
template<typename T>
struct MyClass<T, int>
{
    static constexpr int id = 2;
};

// Two pointer types
template<typename T1, typename T2>
struct MyClass<T1*, T2*>
{
    static constexpr int id = 3;
};

In the MyClass<T1*, T2*> specialization, T1 and T2 are deduced independently. Each of the following cases has an unambiguous result:

static_assert(MyClass<int, float>::id == 0, "primary");
static_assert(MyClass<float, float>::id == 1, "same types");
static_assert(MyClass<float, int>::id == 2, "second is int");
static_assert(MyClass<int*, float*>::id == 3, "two pointers");

No specialization matches MyClass<int, float>, so the primary template is used. Each of the other cases selects the corresponding partial specialization.

Ambiguity and its resolution

A single set of arguments can match multiple partial specializations:

// Ambiguous: both MyClass<T, T> and MyClass<T, int> match.
// MyClass<int, int> first;

// Ambiguous: both MyClass<T, T> and MyClass<T1*, T2*> match.
// MyClass<int*, int*> second;

The compiler compares matching candidates using partial ordering. The program is valid only if one uniquely most specialized implementation can be selected. In both intersections above, neither original candidate is more specialized than the other, so the choice is ambiguous.

Full specialization for a specific pair

The intersection for int, int can be resolved with a full specialization:

template<>
struct MyClass<int, int>
{
    static constexpr int id = 4;
};

static_assert(MyClass<int, int>::id == 4, "full specialization");

A more specialized partial specialization

A more specific partial specialization can be added for two pointers to the same type:

template<typename T>
struct MyClass<T*, T*>
{
    static constexpr int id = 5;
};

static_assert(MyClass<int*, int*>::id == 5, "same pointer types");
static_assert(MyClass<double*, double*>::id == 5, "same pointer types");

The MyClass<T*, T*> pattern is more specialized than both “two identical types” and “two arbitrary pointer types.” It therefore becomes the uniquely most specialized candidate for two pointers to the same type.

The selection algorithm is described in the standard sections on matching partial specializations and partial ordering.

Declaration order

The primary template must be declared first. Its full and partial specializations can only be declared afterward.

Full specialization

A full specialization must be declared before the first use that would otherwise cause the corresponding specialization of the primary template to be implicitly instantiated. This requirement applies in every translation unit where such a use occurs.

A declaration of the primary template is sufficient before a full specialization of the entire class. When an individual member is explicitly specialized, the definition of the class template must be available.

template<typename T>
struct Value
{
    static constexpr int id = 0;
};

template<>
struct Value<int>
{
    static constexpr int id = 1;
};

static_assert(Value<int>::id == 1, "explicit specialization is visible");

Partial specialization

A partial specialization must likewise be declared before the first use for which it is expected to participate in implementation selection. If it is not visible at that point, the program cannot rely on its later declaration.

template<typename T>
struct Category
{
    static constexpr int id = 0;
};

template<typename T>
struct Category<T*>
{
    static constexpr int id = 1;
};

static_assert(Category<int*>::id == 1, "partial specialization is visible");

In practice, a primary template and the specializations intended for general use are usually placed in the same header. This makes the same declarations and selection results available in every translation unit.

The normative requirements are specified separately for explicit specializations and partial specializations.

Key points

  • A primary template defines the general behavior.
  • A full specialization fixes the entire set of arguments and defines an independent class.
  • An individual template member can be fully specialized without replacing the entire class.
  • A partial specialization describes a family of arguments, such as all pointer types.
  • Members of a partial specialization are not inherited from the primary template.
  • When multiple partial specializations match, there must be one uniquely most specialized candidate.
  • Specializations must be declared before the uses for which they are expected to affect implementation selection.
  • Function templates do not support partial specialization; overloading is normally used instead.