C++ Template Specialization | Full vs Partial, Traits, and Pitfalls
이 글의 핵심
Template specialization lets you replace the primary template for specific types or patterns—essential for traits, performance, and domain-specific behavior.
Introduction
Template specialization supplies a different implementation than the primary template for specific types or type patterns. Use it for per-type optimization, exceptions to generic behavior, and type-trait-style metaprogramming.
1. Full specialization
Function template full specialization
#include <iostream>
#include <string>
template<typename T>
void print(T v) {
std::cout << v << std::endl;
}
template<>
void print<const char*>(const char* v) {
std::cout << '"' << v << '"' << std::endl;
}
template<>
void print<std::string>(std::string v) {
std::cout << '"' << v << '"' << std::endl;
}
int main() {
print(42);
print(3.14);
print("hello");
print(std::string("world"));
return 0;
}
Resolution: The most specific viable specialization wins (e.g. const char* vs the primary T).
Class template full specialization
template<typename T>
class Storage {
T data;
public:
Storage(T d) : data(d) {}
void print() {
std::cout << "Generic: " << data << std::endl;
}
};
template<>
class Storage<bool> {
bool data;
public:
Storage(bool d) : data(d) {}
void print() {
std::cout << "Bool: " << (data ? "true" : "false") << std::endl;
}
bool get() const { return data; }
};
2. Partial specialization
Pointer partial specialization
template<typename T>
class Wrapper {
T value;
public:
Wrapper(T v) : value(v) {}
void print() {
std::cout << "Value: " << value << std::endl;
}
};
template<typename T>
class Wrapper<T*> {
T* ptr;
public:
Wrapper(T* p) : ptr(p) {}
void print() {
if (ptr) {
std::cout << "Pointer: *" << *ptr << " (at " << ptr << ")" << std::endl;
} else {
std::cout << "Pointer: nullptr" << std::endl;
}
}
};
Array partial specialization
template<typename T>
struct ArrayTraits {
static constexpr bool is_array = false;
static constexpr size_t size = 0;
};
template<typename T, size_t N>
struct ArrayTraits<T[N]> {
static constexpr bool is_array = true;
static constexpr size_t size = N;
using element_type = T;
};
3. Practical example: type traits
is_pointer sketch
template<typename T>
struct is_pointer {
static constexpr bool value = false;
};
template<typename T>
struct is_pointer<T*> {
static constexpr bool value = true;
};
remove_const sketch
template<typename T>
struct remove_const {
using type = T;
};
template<typename T>
struct remove_const<const T> {
using type = T;
};
4. Common problems
Problem 1: No partial specialization for function templates
Use overloads instead:
template<typename T>
void func(T value) { /* generic */ }
template<typename T>
void func(T* value) { /* pointer */ }
Problem 2: Overload / specialization precedence
Roughly: full specialization beats partial beats primary—see the standard’s partial ordering rules for your exact case.
Problem 3: ODR and specializations in headers
Avoid multiple definitions of the same full specialization across TUs unless inline rules apply—often declare in a header and define in one .cpp, or mark appropriately.
Problem 4: Ambiguous partial specializations
Disambiguate with a more specific full specialization or by refactoring patterns.
5. Example: serialization system
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
template<typename T>
struct Serializer {
static std::string serialize(const T& value) {
std::ostringstream oss;
oss << value;
return oss.str();
}
};
template<>
struct Serializer<std::string> {
static std::string serialize(const std::string& value) {
return "\"" + value + "\"";
}
};
template<>
struct Serializer<bool> {
static std::string serialize(const bool& value) {
return value ? "true" : "false";
}
};
template<typename T>
struct Serializer<std::vector<T>> {
static std::string serialize(const std::vector<T>& vec) {
std::string result = "[";
for (size_t i = 0; i < vec.size(); ++i) {
if (i > 0) result += ", ";
result += Serializer<T>::serialize(vec[i]);
}
result += "]";
return result;
}
};
template<typename T>
struct Serializer<T*> {
static std::string serialize(T* const& ptr) {
if (ptr) {
return "*" + Serializer<T>::serialize(*ptr);
}
return "null";
}
};
6. Example: type-trait-style library
#include <iostream>
#include <type_traits>
template<typename T>
struct TypeTraits {
static constexpr bool is_pointer = false;
static constexpr bool is_const = false;
static constexpr bool is_array = false;
static constexpr size_t size = sizeof(T);
static std::string name() { return "Unknown"; }
};
template<typename T>
struct TypeTraits<T*> {
static constexpr bool is_pointer = true;
static constexpr bool is_const = false;
static constexpr bool is_array = false;
static constexpr size_t size = sizeof(void*);
static std::string name() {
return TypeTraits<T>::name() + "*";
}
};
template<typename T>
struct TypeTraits<const T> {
static constexpr bool is_pointer = TypeTraits<T>::is_pointer;
static constexpr bool is_const = true;
static constexpr bool is_array = TypeTraits<T>::is_array;
static constexpr size_t size = sizeof(T);
static std::string name() {
return "const " + TypeTraits<T>::name();
}
};
template<typename T, size_t N>
struct TypeTraits<T[N]> {
static constexpr bool is_pointer = false;
static constexpr bool is_const = false;
static constexpr bool is_array = true;
static constexpr size_t size = sizeof(T) * N;
static std::string name() {
return TypeTraits<T>::name() + "[" + std::to_string(N) + "]";
}
};
template<>
struct TypeTraits<int> {
static constexpr bool is_pointer = false;
static constexpr bool is_const = false;
static constexpr bool is_array = false;
static constexpr size_t size = sizeof(int);
static std::string name() { return "int"; }
};
template<>
struct TypeTraits<double> {
static constexpr bool is_pointer = false;
static constexpr bool is_const = false;
static constexpr bool is_array = false;
static constexpr size_t size = sizeof(double);
static std::string name() { return "double"; }
};
Summary
- Full specialization: alternate implementation for one concrete substitution.
- Partial specialization: alternate implementation for a pattern (class templates only).
- Functions: no partial specialization—use overloading.
- Precedence: more specific template wins.
- Production: traits, serialization, optimized paths.
Comparison
| Full | Partial | |
|---|---|---|
| Applies to | Function or class templates | Class templates only |
| Matches | Concrete type (int, bool) | Pattern (T*, const T) |
| Syntax | template<> | template<typename T> |
Related posts
- Template basics
- SFINAE
- Type traits
Keywords
C++, template specialization, partial specialization, full specialization, traits.
See also
autodeduction- CTAD
- C++20 Concepts
constexpr if- CRTP