본문으로 건너뛰기 C++ Template Specialization | Full vs Partial· Traits

C++ Template Specialization | Full vs Partial· Traits

C++ Template Specialization | Full vs Partial· Traits

이 글의 핵심

Master template specialization in C++: full and partial specialization, overload resolution vs partial ordering, ODR, ambiguity, and real patterns for traits and serialization.

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>

Keywords

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

See also


Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Master template specialization in C++: full and partial specialization, overload resolution vs partial ordering, ODR, am…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, Templates, Specialization, Partial Specialization, SFINAE.