Template Specialization — Full and Partial
A function or class template defines the general case — what to do for any type. But sometimes a specific type (or family of types) requires fundamentally different behavior: a hash function for std::string is nothing like a hash for an arbitrary T, and a container of bool can be bit-packed in a way a container of int cannot. Template specializationlets you provide a custom implementation for specific types while keeping the general template for everything else.
Full (explicit) specialization
A full specialization provides an alternative definition for a specific, fully determined set of template arguments. The template parameter list is empty (template<>) and the specialized arguments appear in angle brackets after the name.
// Primary template: generic hash using runtime computation
template<typename T>
struct Hash {
std::size_t operator()(const T& val) const {
// generic fallback — hash the bytes of the object
const auto* bytes = reinterpret_cast<const unsigned char*>(&val);
std::size_t h = 0;
for (std::size_t i = 0; i < sizeof(T); ++i)
h = h * 31 + bytes[i];
return h;
}
};
// Full specialization for std::string — completely different implementation:
template<>
struct Hash<std::string> {
std::size_t operator()(const std::string& s) const {
std::size_t h = 0;
for (char c : s)
h = h * 31 + static_cast<unsigned char>(c);
return h;
}
};
// Full specialization for int — just return the value itself:
template<>
struct Hash<int> {
std::size_t operator()(int val) const {
return static_cast<std::size_t>(val);
}
};
Hash<double> hd; // uses primary template
Hash<std::string> hs; // uses string specialization
Hash<int> hi; // uses int specializationPartial specialization (class templates only)
A partial specialization matches a family of types — all pointers, all references, or all types with a specific structure — rather than a single type. Partial specialization is only available for class templates, not function templates (use overloading for functions).
// Primary template:
template<typename T>
struct IsPointer {
static constexpr bool value = false;
};
// Partial specialization matching any pointer type T*:
template<typename T>
struct IsPointer<T*> {
static constexpr bool value = true;
};
std::cout << IsPointer<int>::value; // false — primary
std::cout << IsPointer<int*>::value; // true — partial specialization
std::cout << IsPointer<char*>::value; // true — matches T* for T=char
// Partial specialization on multiple parameters:
template<typename T, typename U>
struct IsSame { static constexpr bool value = false; };
template<typename T>
struct IsSame<T, T> { static constexpr bool value = true; };
std::cout << IsSame<int, int>::value; // true — both are T=int
std::cout << IsSame<int, long>::value; // false — different typesHow the compiler selects a specialization
When the compiler encounters a template instantiation, it ranks the available options: a full specialization beats a partial specialization which beats the primary template. Among multiple partial specializations, the most specialized one wins (the one that matches the fewest possible types). If two partial specializations are equally good, the compilation fails with an ambiguity error.
// Primary
template<typename T, typename U> struct Traits { ... };
// Partial: one arg is pointer
template<typename T, typename U> struct Traits<T*, U> { ... };
// Partial: both args are same type
template<typename T> struct Traits<T, T> { ... };
// Full: exactly int, double
template<> struct Traits<int, double> { ... };
Traits<int, double> // uses full specialization (most specific)
Traits<int*, int*> // AMBIGUOUS: matches T* and T,T — compile error
Traits<int*, double> // uses T* partial
Traits<float, float> // uses T,T partial
Traits<int, long> // uses primaryWhere specialization appears in practice
std::hash<T>The standard library's hash function is a class template. std::unordered_map uses std::hash<Key>. You specialize it to add hash support for custom types.
std::numeric_limits<T>Provides type-specific constants (max, min, infinity). Fully specialized for each numeric type.
std::vector<bool>A controversial full specialization that bit-packs booleans. Often cited as an example of when specialization causes surprising behavior.
Type traits (std::is_pointer, std::is_integral, ...)Almost all type traits in <type_traits> are implemented using partial and full specializations of class templates.
Key rules to remember
Full specialization (template<>) matches exactly one set of template arguments
It is completely independent from the primary template — it can have a totally different body, different members, different base classes.
Partial specialization only applies to class templates, not function templates
For functions, use overloading to achieve type-specific behavior. Partial specialization of function templates is not allowed in C++.
The compiler picks the most specialized matching template
Full > partial > primary. Among partial specializations, the one matching the fewest types wins. Ambiguity between equally good partials is a compile error.
Specialize std::hash<YourType> to make your types work with unordered containers
template<> struct std::hash<MyType> { std::size_t operator()(const MyType& v) const { ... } }; — place this in the same header as MyType.