C++ Template Specialization | Full vs Partial, Traits, and Pitfalls

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

  1. Full specialization: alternate implementation for one concrete substitution.
  2. Partial specialization: alternate implementation for a pattern (class templates only).
  3. Functions: no partial specialization—use overloading.
  4. Precedence: more specific template wins.
  5. Production: traits, serialization, optimized paths.

Comparison

FullPartial
Applies toFunction or class templatesClass templates only
MatchesConcrete type (int, bool)Pattern (T*, const T)
Syntaxtemplate<>template<typename T>

  • Template basics
  • SFINAE
  • Type traits

Keywords

C++, template specialization, partial specialization, full specialization, traits.

See also

  • auto deduction
  • CTAD
  • C++20 Concepts
  • constexpr if
  • CRTP