Skip to content
C++
Language
since C++98
Intermediate

Template Specialization

Full and partial specialization, explicit instantiation, specialization ordering, and when to prefer overloads — with C++ version discipline.

Template Specializationsince C++98

Template specialization provides an alternative definition of a primary template for specific template arguments — either exact types (full specialization) or families of types matching a pattern (partial specialization, classes and variable templates only).

Overview

Template specialization is the mechanism by which a generic template is replaced with a hand-written implementation for particular arguments. The compiler performs pattern matching at instantiation time: it finds all full and partial specializations that match the requested arguments, then selects the most specialized match; if no specialization matches, the primary template is used.

Specialization is not the same as instantiation. Instantiation is the compiler generating code from a template for a specific argument set. Specialization is you, the programmer, supplying that code manually.

There are three distinct forms:

FormApplies toSyntax
Full (explicit) specializationclass, function, variable templatestemplate<>
Partial specializationclass and variable templates onlytemplate<typename T> with constrained argument list
Explicit instantiationclass and function templatestemplate class X<int>;

Syntax

Full specialization:

cpp
template<>
struct TypeTraits<int> { /* ... */ };     // class

template<>
void serialize<MyType>(MyType&) { /* ... */ }; // function

Partial specialization (class/variable templates only):

cpp
template<typename T>
struct Storage<T*> { /* pointer specialization */ };

template<typename T, typename Alloc>
struct Container<T, Alloc, true> { /* third param fixed */ };

Explicit instantiation:

cpp
extern template class std::vector<MyType>;  // suppress in this TU
template class std::vector<MyType>;          // emit in exactly one TU

Examples

Full Specialization

Full specialization provides an exact match for one combination of template arguments. The template<> prefix is mandatory.

cpp
#include <string_view>

template<typename T>
struct TypeName { static constexpr std::string_view value = "unknown"; };

template<> struct TypeName<int>    { static constexpr std::string_view value = "int"; };
template<> struct TypeName<double> { static constexpr std::string_view value = "double"; };
template<> struct TypeName<bool>   { static constexpr std::string_view value = "bool"; };

// C++17: constexpr if is often cleaner for small type switches,
// but full specialization remains the right tool when the body is large
// or when you need the type to be a complete class with members.
static_assert(TypeName<int>::value == "int");
static_assert(TypeName<float>::value == "unknown");

Partial Specialization — Pattern Matching

The compiler chooses the most specialized matching partial specialization. "Most specialized" means the pattern places the strictest constraints on the template arguments.

cpp
// Primary: generic pair
template<typename T, typename U>
struct Pair { using type = T; /* ... */ };

// Partial: both types identical
template<typename T>
struct Pair<T, T> { using type = T; /* can optimize */ };

// Partial: second type is a pointer
template<typename T, typename U>
struct Pair<T, U*> { using ptr_type = U; };

// Partial: both pointers — more specialized than Pair<T, U*>
template<typename T, typename U>
struct Pair<T*, U*> { using first_ptr = T; using second_ptr = U; };

Pair<int, double>   a;  // primary
Pair<int, int>      b;  // Pair<T, T>
Pair<int, float*>   c;  // Pair<T, U*>
Pair<int*, float*>  d;  // Pair<T*, U*> — wins (more constrained)

If two partial specializations are equally specific for a given instantiation, the compiler emits an ambiguity error — no implicit tiebreaking.

Specialization for Pointers vs Values

A canonical pattern: store values by value, store pointees via unique ownership.

cpp
#include <memory>

template<typename T>
class Registry {
    T value_;
public:
    void set(T v)   { value_ = std::move(v); }  // C++11 move
    T const& get() const { return value_; }
};

// Specialization: take ownership of the pointed-to object
template<typename T>
class Registry<T*> {
    std::unique_ptr<T> ptr_;  // C++11
public:
    void set(T* raw)  { ptr_.reset(raw); }
    T const& get() const { return *ptr_; }
};

Registry<int>    r1; r1.set(42);
Registry<int*>   r2; r2.set(new int{7});  // ownership transferred

Variable Template Specialization (C++14)

Variable templates introduced in C++14 support both full and partial specialization.

cpp
// C++14
template<typename T>
constexpr bool is_trivially_relocatable = std::is_trivially_copyable_v<T>;  // C++17 _v helper

// Partial: pointers are always trivially relocatable
template<typename T>
constexpr bool is_trivially_relocatable<T*> = true;

// Full: a known-safe type
struct Vec2 { float x, y; };
template<>
constexpr bool is_trivially_relocatable<Vec2> = true;

Specializing Standard Library Traits

The standard permits user-defined specializations of std::hash, std::less (by policy), and std::numeric_limits for user types. Specializing most other std:: templates is undefined behaviour.

cpp
#include <functional>
#include <unordered_map>

struct EntityId {
    uint32_t world;
    uint32_t index;
    bool operator==(EntityId const&) const = default;  // C++20
};

template<>
struct std::hash<EntityId> {
    std::size_t operator()(EntityId const& id) const noexcept {
        // Combine two 32-bit values into a single hash.
        // Bit-mixing avoids collisions when world/index share low bits.
        std::size_t h = std::hash<uint32_t>{}(id.world);
        h ^= std::hash<uint32_t>{}(id.index) + 0x9e3779b9 + (h << 6) + (h >> 2);
        return h;
    }
};

std::unordered_map<EntityId, std::string> names;
names[{0, 1}] = "player";

Explicit Instantiation — Controlling Code Generation

Explicit instantiation separates the where of template code generation from the when of its use. Without it, every translation unit (TU) that uses vector<MyType> emits and discards its own copy of every vector<MyType> member function.

cpp
// mytypes.h
#include <vector>
struct Particle { float x, y, z; float mass; };

// Suppress implicit instantiation in every TU that includes this header.
// C++11 extern template
extern template class std::vector<Particle>;

// physics.cpp — the one TU that emits the full instantiation
#include "mytypes.h"
template class std::vector<Particle>;  // explicit instantiation definition

// result: smaller .o files and faster link times for large projects

The extern template declaration (C++11) must precede any use of vector<Particle> that would trigger implicit instantiation; the explicit instantiation definition must appear in exactly one TU.

Best Practices

Prefer overloads over function template specializations. Function template specialization interacts poorly with overload resolution: a non-template overload always wins against a template (desired), but a function template specialization does not participate in overload resolution — it is selected after the primary template has already been chosen, bypassing any non-template overloads added later. The result is surprising call dispatch.

cpp
template<typename T> void log(T x)   { /* primary */ }
template<>           void log(int x) { /* specialization — silent if an
                                          overload of log(int) is added
                                          later in another header */ }

// CORRECT: plain overload
void log(int x) { /* always preferred over the template */ }

Use if constexpr (C++17) for small per-type branching inside a single function body. Reserve specialization for cases where the interface, data layout, or class hierarchy genuinely differs between types.

cpp
// C++17
template<typename T>
void serialize(std::ostream& out, T const& val) {
    if constexpr (std::is_arithmetic_v<T>) {          // C++17
        out.write(reinterpret_cast<char const*>(&val), sizeof(T));
    } else if constexpr (requires { val.serialize(out); }) {  // C++20 requires-expression
        val.serialize(out);
    } else {
        static_assert(sizeof(T) == 0, "T is not serializable");
    }
}

Declare full specializations in the header, define them in a single .cpp. If a full specialization is defined in a header included by multiple TUs, you get an ODR violation. Forward-declare with extern or define inline/constexpr.

Common Pitfalls

Partial specialization on function templates is illegal. You can fully specialize a function template, but not partially. Workaround: delegate to a class template helper.

cpp
// Won't compile — partial function template specialization is not allowed
template<typename T>
void process(T* x) { }  // this is an overload, not a specialization

// Correct pattern: partial specialization lives on a struct
template<typename T, bool IsPtr>
struct ProcessImpl { static void run(T x) { /* value */ } };

template<typename T>
struct ProcessImpl<T, true> { static void run(T x) { /* pointer */ } };

template<typename T>
void process(T x) { ProcessImpl<T, std::is_pointer_v<T>>::run(x); }  // C++17

Specialization must be more specialized, not just different. If a partial specialization is not strictly more constrained than the primary for any argument pattern, you get an error at instantiation when both match equally.

Explicit instantiation of a specialization requires the specialization to have been declared first. If the compiler sees template class X<int> before template<> class X<int>, it instantiates the primary, not the specialization.

static_assert(false) in an unselected if constexpr branch still fires (pre-C++23). Use a type-dependent expression instead: static_assert(sizeof(T) == 0, ...). C++23 introduces static_assert(false) being valid in a discarded branch of if constexpr.

See Also

  • CRTP — partial specialization enables the Curiously Recurring Template Pattern
  • Type Erasure — specialization used to dispatch to type-specific storage
  • if constexpr — C++17 alternative for compile-time branching inside templates
  • Templates — primary templates, template parameters, and instantiation