본문으로 건너뛰기 C++ Class Templates — Complete Guide

C++ Class Templates — Complete Guide

C++ Class Templates — Complete Guide

Introduction: Int stack, double stack—copy/paste forever?

Stop duplicating Stack for every type

You wrote IntStack, DoubleStack, same logic—only T differs. Class templates are the cookie cutter: one pattern, many baked shapes. You write Stack, Stack, Stackstd::string once; the compiler instantiates a concrete class at each use.

// g++ -std=c++17 -o stack_tpl stack_tpl.cpp && ./stack_tpl
#include <vector>
#include <iostream>
#include <string>
#include <stdexcept>
// 실행 예제
template <typename T>
class Stack {
    std::vector<T> data;
public:
    void push(const T& value) { data.push_back(value); }
    T pop() {
        if (empty()) throw std::logic_error("Stack is empty");
        T value = data.back();
        data.pop_back();
        return value;
    }
    bool empty() const { return data.empty(); }
    size_t size() const { return data.size(); }
};
int main() {
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    std::cout << intStack.pop() << "\n";
    Stack<std::string> strStack;
    strStack.push("hello");
    strStack.push("world");
    std::cout << strStack.pop() << "\n";
    return 0;
}

Unlike function templates, you usually name the template arguments explicitly: Stack. C++17 CTAD can deduce in some cases from constructors.

flowchart TB
  subgraph template[Template definition]
    T["template \nclass Stack"]
  end
  subgraph instances[Instantiated types]
    I1[Stack]
    I2[Stack]
    I3[Stack]
  end
  T -->|T=int| I1
  T -->|T=double| I2
  T -->|T=std::string| I3

Table of contents

  1. Basic syntax
  2. Member definitions outside class
  3. Partial specialization
  4. Template aliases
  5. Practical generic containers
  6. Complete Stack/Array/traits
  7. Common errors
  8. Best practices
  9. Production patterns

1. Basic syntax

template <typename T>
class Box {
    T value;
public:
    Box(const T& v) : value(v) {}
    T get() const { return value; }
    void set(const T& v) { value = v; }
};

Multiple parameters:

template <typename K, typename V>
class KeyValue { /* ... */ };

Default template arguments:

template <typename T, typename Container = std::vector<T>>
class Stack { /* ... */ };

2. Out-of-line member definitions

template <typename T>
void Container<T>::set(const T& v) {
    value = v;
}

Templates: definitions typically stay in headers (same TU) to avoid link errors. Static data members:

template <typename T>
int Counter<T>::count = 0;

3. Partial specialization

Specialize for patterns (e.g. T*, T[], Pair<T,T>)—not in the primary template. Note: A common pattern uses SmartPtr<T[]>-style partial specialization for array delete semantics—match your design to delete vs delete[].

4. Template aliases

template <typename T>
using Vec = std::vector<T>;
template <typename K, typename V>
using Map = std::unordered_map<K, V>;
template <typename T>
using StringMap = std::unordered_map<std::string, T>;

5. Practical generic containers

A fixed-capacity ring buffer is a good example of a class template that earns its genericity — the same logic works for int, std::string, or any movable type, and the capacity itself can be a non-type template parameter.

template <typename T, size_t Capacity>
class RingBuffer {
    std::array<T, Capacity> data{};
    size_t head_ = 0, count_ = 0;
public:
    void push(const T& value) {
        data[(head_ + count_) % Capacity] = value;
        if (count_ < Capacity) ++count_;
        else head_ = (head_ + 1) % Capacity;  // overwrite oldest
    }
    T& front() { return data[head_]; }
    size_t size() const { return count_; }
    bool full() const { return count_ == Capacity; }
};
// RingBuffer<int, 4> for a fixed-size event log
// RingBuffer<std::string, 16> for a recent-commands history

Why a class template here: a function template can’t hold state between calls, and a non-template class would force one copy-pasted RingBuffer per (T, Capacity) pair.

6. Complete Stack/Array/traits example

Putting partial specialization, template aliases, and traits together in one self-contained example:

#include <vector>
#include <array>
#include <type_traits>
// Primary template: growable stack backed by std::vector
template <typename T, typename Container = std::vector<T>>
class Stack {
    Container data;
public:
    void push(const T& v) { data.push_back(v); }
    void pop() { data.pop_back(); }
    T& top() { return data.back(); }
    bool empty() const { return data.empty(); }
};
// Partial specialization: fixed-size stack for a raw array backing store
template <typename T, size_t N>
class Stack<T, std::array<T, N>> {
    std::array<T, N> data{};
    size_t count_ = 0;
public:
    void push(const T& v) { if (count_ < N) data[count_++] = v; }
    void pop() { if (count_ > 0) --count_; }
    T& top() { return data[count_ - 1]; }
    bool empty() const { return count_ == 0; }
};
// Trait: detect whether a type is stack-like (has push/pop/top)
template <typename, typename = void>
struct is_stack_like : std::false_type {};
template <typename T>
struct is_stack_like<T, std::void_t<decltype(std::declval<T>().push(std::declval<typename T::value_type>()))>>
    : std::true_type {};
int main() {
    Stack<int> heapStack;            // uses std::vector<int> backing
    heapStack.push(1);
    heapStack.push(2);
    Stack<int, std::array<int, 8>> fixedStack;  // uses partial specialization
    fixedStack.push(1);
    fixedStack.push(2);
    return 0;
}

This mirrors how the standard library itself composes: std::stack is a container adapter over a configurable backing container, exactly like the Stack<T, Container> shown here.

7. Common errors

  • Undefined reference to template member defined only in .cpp → put definition in header or explicit instantiation
  • Dependent namestypename / template keyword
  • >> in nested templates → fine in C++11+ (was > > in C++03)
  • CTAD fails for default-constructed Box b when T can’t be deduced → write Box b

8. Best practices

  • Prefer typename for type parameters
  • static_assert constraints
  • using for value_type, iterators inside containers
  • if constexpr (C++17) for type-specific branches

9. Production patterns

  • CRTP for static polymorphism
  • Policy-based design (container type as template parameter)
  • Explicit instantiation in .cpp for selected types to reduce compile time:
template class Stack<int>;
template class Stack<std::string>;

Keywords

C++ class template, template class, partial specialization, generic container, template alias, type traits, CRTP, policy design

Summary

TopicDetail
Syntaxtemplate <typename T> class C { };
UseC<int> obj; (CTAD exceptions in C++17)
MembersOut-of-line definitions need template<typename T> and C<T>::
Partial specPattern-based specializations
Aliasesusing Alias = Template<T>;
One-line summary: One class template replaces many copy-pasted classes; specialize patterns; keep definitions visible to the compiler.
Next: Variadic templates #9-3

FAQ

When is this useful?

A. Building reusable containers, wrappers, and type-safe APIs—core of STL-style design.

Read first?

A. Template basics, series index.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, Class Template, Partial Specialization, template, using alias, CRTP.