C++ • C++-Specific Topics

Class Templates — Use class templates (intro)

Write one class definition that works with any data type through compile-time parameterization.

Programming braces with a command terminal and binary digits.
Inside this lesson 10 sections 5 questions 7 figures

SECTION 1

Historical Context & Motivation

Before templates existed in C++, writing a generic container—say, a stack that could hold integers, doubles, or strings—required either duplicating the entire class definition for each type or resorting to void* pointers that sacrificed type safety. Both approaches were error-prone, tedious to maintain, and fundamentally at odds with C++'s philosophy of zero-cost abstractions. The desire for type-safe generic programming without runtime overhead drove the development of templates as a core language feature.

1979 • C with Classes

Bjarne Stroustrup begins work on "C with Classes" at Bell Labs. No generic programming support exists; developers duplicate code or use macros for type-generic data structures.

1986 • Templates Proposed

Stroustrup proposes parameterized types (templates) for C++, inspired by Ada generics and ML's parametric polymorphism, aiming to enable type-safe containers without runtime cost.

1990 • Cfront 3.0

The Cfront 3.0 compiler ships with the first implementation of function and class templates, allowing early adopters to experiment with generic programming in practice.

1998 • C++98 Standard & STL

The first ISO C++ standard (C++98) officially standardizes templates. Alexander Stepanov's Standard Template Library (STL) showcases their power with containers like std::vector<T> and algorithms like std::sort.

2011–2020 • Modern Template Evolution

C++11 introduces variadic templates and type traits; C++17 adds class template argument deduction (CTAD); C++20 brings concepts for constraining template parameters, completing the arc from raw parameterized types to a mature generic programming paradigm.

The central question that class templates answer is deceptively simple: how can we write a single class definition that the compiler specializes for each concrete type at compile time, preserving full type safety and incurring no runtime overhead? Understanding class templates is essential because they form the backbone of the STL and virtually every modern C++ library.

SECTION 2

Core Principles & Definitions

A class template is a blueprint for generating classes. Rather than defining a concrete class, you define a pattern parameterized by one or more template parameters—most commonly type parameters introduced with typename or class. When you write MyClass<int>, the compiler performs template instantiation: it substitutes int for every occurrence of the type parameter and generates a fully concrete class. This process happens entirely at compile time, which means template-generated code runs at the same speed as hand-written, type-specific code.

1

Template Declaration

The template <typename T> prefix introduces a type parameter T that can be used throughout the class body as a placeholder for any type.
2

Implicit Instantiation

When you use Stack<double>, the compiler automatically generates a concrete class by substituting double for T. Only member functions that are actually called get instantiated.
3

Type Safety

Unlike void* approaches, the compiler enforces that a Stack<int> only accepts int values. Misuse is caught at compile time, not at runtime.
4

Code Reuse Without Inheritance

Templates provide parametric polymorphism—one definition serves many types without virtual dispatch, vtable overhead, or an inheritance hierarchy.
5

Header-Only Convention

Because the compiler needs the full template definition at each point of use, class templates are typically defined entirely in header files (.h or .hpp), not split into .cpp files.
✦

KEY TAKEAWAY

Think of a class template as a cookie cutter and each instantiation as a cookie made from different dough. The cutter (template) defines the shape—member variables, member functions, access levels—while the dough (type argument) determines the material. A Stack<int> cookie and a Stack<string> cookie have the same structural shape but hold entirely different data, and both are produced from the same single cutter definition.

SECTION 3

Visual Explanation — From Template to Concrete Classes

Class Template Instantiation FlowCLASS TEMPLATE (Blueprint)template <typename T>class Stack { T data[100]; int top;};T = intT = doubleT = std::stringStack<int>int data[100];int top;Concrete class #1Stack<double>double data[100];int top;Concrete class #2Stack<string>string data[100];int top;Concrete class #3Each instantiation is a distinct, independent type at compile time
The violet box at the top represents the single class template definition. Each arrow represents an instantiation where the compiler substitutes a concrete type for T, producing three entirely separate classes—Stack<int>, Stack<double>, and Stack<string>—each with its own member variable types.

The diagram above illustrates the fundamental mechanism of template instantiation. Notice that every occurrence of T in the template body is replaced with the concrete type argument. The resulting classes—Stack<int> and Stack<double>—are entirely distinct types in the C++ type system; you cannot assign one to the other. This is a critical distinction from runtime polymorphism via inheritance, where a base pointer can refer to any derived class. With templates, the compiler resolves everything statically, and there is no shared base class unless you explicitly design one.

SECTION 4

How Class Templates Work — Syntax & Instantiation

Template Declaration Syntax

Every class template begins with a template parameter list enclosed in angle brackets, preceded by the keyword template. Within this list, each type parameter is introduced with either typename or class—the two keywords are interchangeable in this context. The parameter name (conventionally T, U, etc.) then serves as a placeholder throughout the class body.

BASIC CLASS TEMPLATE FORM

template <typename T> class ClassName { T memberVariable; public: void setData(const T& val); T getData() const; };

T — a type parameter that will be replaced by the actual type at instantiation. ClassName — the name of the template, which is not itself a type until instantiated.

Defining Member Functions Outside the Class

When you define a member function outside the template class body, you must repeat the template parameter list and qualify the class name with ClassName<T>. This tells the compiler that the function belongs to the template, not to some ordinary class. Forgetting this qualification is one of the most common errors encountered by students new to templates.

OUT-OF-CLASS MEMBER DEFINITION

template <typename T> void ClassName<T>::setData(const T& val) { memberVariable = val; }

The template <typename T> prefix must precede every out-of-class member definition. The scope resolution uses ClassName<T>:: rather than just ClassName::.

Instantiation and the One-Definition Rule

When the compiler encounters ClassName<int> obj;, it triggers implicit instantiation. The compiler substitutes int for T and generates the full class. Crucially, only those member functions that are actually called are instantiated—unused member functions remain uncompiled. This means a template can contain member functions that would fail to compile for certain types, as long as those functions are never invoked with those types. The One-Definition Rule (ODR) still applies, but the standard permits identical template instantiations in multiple translation units; the linker deduplicates them.

⚠

Common Pitfall

Separating a class template's definition into a .h and .cpp file the way you would with an ordinary class will result in linker errors ("undefined reference"). Because the compiler needs the full definition to instantiate the template, the entire template—including member function bodies—must be visible in every translation unit that uses it. The standard practice is to define everything in the header file.

SECTION 5

Anatomy of a Complete Class Template

To solidify the syntax, let us examine a complete class template for a simple Pair class that stores two values of potentially different types. This demonstrates multiple template parameters, constructor definitions, and accessor methods—all the essential pieces you will encounter in real-world template code.

Annotated Class Template: Pair<T1, T2>template <typename T1, typename T2>class Pair {private:T1 first_;T2 second_;public:Pair(const T1& a, const T2& b): first_(a), second_(b) {}T1 getFirst() const {return first_;}T2 getSecond() const {return second_;}};// Usage:Pair<int, std::string> p(42, "hello");TEMPLATE PARAMETER LISTTwo type params: T1 and T2DATA MEMBERSTypes depend on T1, T2CONSTRUCTORUses const ref for efficiencyACCESSORSReturn types match paramsINSTANTIATIONT1=int, T2=std::string
This annotated listing shows a complete Pair<T1, T2> class template with two type parameters. The annotations on the right highlight five key structural elements: the template parameter list, data members whose types depend on the parameters, a constructor taking const references, accessor functions whose return types match the parameters, and the instantiation syntax at the bottom.

Several details in this example merit closer attention. First, the constructor takes its parameters by const reference (const T1&), which avoids unnecessary copies when T1 or T2 is a large or expensive-to-copy type such as std::string. Second, the accessor methods are marked const because they do not modify the object's state. Third, the template uses two independent type parameters, meaning Pair<int, double> and Pair<double, int> are distinct types—the order of the type arguments matters.

Syntax ElementMeaningExample
template <typename T>Declares a template with one type parametertemplate <typename T> class Box { T val; };
ClassName<int>Instantiates the template with a concrete typeBox<int> b;
ClassName<T>::func()Scope-qualified out-of-class member definitiontemplate<typename T> T Box<T>::get() { ... }
template <typename T1, typename T2>Multiple type parametersPair<int, string> p(1, "a");

Key syntax elements of class templates

SECTION 6

Worked Example — Building a Generic Stack

Let us build a Stack<T> class template from scratch, exercising all the patterns discussed so far. The stack will support push, pop, top, and isEmpty operations, backed by a std::vector<T>.

Implementing Stack<T>

  1. 1

    Step 1 — Declare the Template and Data Members

    We begin with the template prefix and define a private std::vector<T> to store the elements. Using vector instead of a raw array gives us dynamic sizing automatically. template <typename T> class Stack { private: std::vector<T> elems_; };
    Template declared with one type parameter T and a vector-based backing store.
  2. 2

    Step 2 — Define push() and pop()

    The push method takes a const T& and delegates to vector::push_back. The pop method checks for emptiness (defensive programming) before calling vector::pop_back. void push(const T& elem) { elems_.push_back(elem); } void pop() { if (elems_.empty()) throw std::out_of_range("Stack::pop(): empty stack"); elems_.pop_back(); }
    Two mutating methods defined inline within the class body.
  3. 3

    Step 3 — Define top() and isEmpty()

    The top method returns a const reference to the last element. Returning by reference avoids copying large objects. The isEmpty method delegates directly to vector::empty. const T& top() const { if (elems_.empty()) throw std::out_of_range("Stack::top(): empty stack"); return elems_.back(); } bool isEmpty() const { return elems_.empty(); }
    Non-mutating accessors: top() returns const T&; isEmpty() returns bool.
  4. 4

    Step 4 — Instantiate and Use the Template

    In main() we instantiate Stack<int> and Stack<std::string> to verify that the same template works with fundamentally different types. int main() { Stack<int> intStack; intStack.push(7); intStack.push(42); std::cout << intStack.top() << '\n'; // 42 Stack<std::string> strStack; strStack.push("hello"); strStack.push("world"); std::cout << strStack.top() << '\n'; // world }
    Output: 42 followed by world. The compiler generated two independent Stack classes, one for int and one for std::string.

SECTION 7

Class Templates vs. Alternatives

Class templates are not the only mechanism for writing type-generic code in C++. Before templates, programmers relied on preprocessor macros, void* pointers, or inheritance hierarchies with virtual functions. Each approach trades off differently along the axes of type safety, runtime performance, and code maintainability. The table below provides a systematic comparison.

CriterionClass Templatesvoid* ApproachInheritance + Virtual
Type SafetyFull compile-time type checking; errors caught before executionNo type safety; incorrect casts cause undefined behavior at runtimeType-safe through base pointer, but downcasting (dynamic_cast) needed for specific types
Runtime OverheadZero — all resolved at compile timeMinimal pointer indirection, but manual casts add fragilityVirtual dispatch via vtable; pointer indirection on every call
Code Bloat RiskEach instantiation generates new code; potential binary size increaseSingle implementation shared across all typesSingle implementation; vtable adds small per-class overhead
Error MessagesHistorically verbose; C++20 concepts greatly improve diagnosticsErrors surface at runtime, not compile timeClear compile-time errors through interface contracts
FlexibilityWorks with any type that satisfies the implicit interface (duck typing at compile time)Works with any type, but no constraintsRequires explicit inheritance from a common base class

Comparison of generic programming approaches in C++

✦

KEY TAKEAWAY

Class templates occupy a unique niche: they deliver the type safety of static typing combined with the flexibility of generic code, all without any runtime cost. Think of it like a custom-tailored suit versus a one-size-fits-all garment (void*) or a modular uniform system (inheritance): the tailor (compiler) measures your exact type and produces a perfectly fitted garment at compile time, so there is no adjustment needed when you wear it (at runtime).

SECTION 8

Connection to Advanced Template Features

The basic class template introduced in this lesson is the foundation upon which a rich ecosystem of advanced C++ techniques is built. Understanding where these introductory concepts lead will help you appreciate why mastering the fundamentals is so important. The table below maps each introductory concept to its advanced counterpart.

Introductory ConceptAdvanced ExtensionWhat It Enables
Single type parameter <typename T>Non-type template parametersParameterize on values (e.g., Array<int, 10>) for compile-time-sized containers
Implicit interface (duck typing)C++20 ConceptsExplicit constraints on template parameters with clear error messages when requirements are not met
One definition for all typesTemplate SpecializationOverride the template for specific types (e.g., optimize Stack<bool> to use bitwise packing)
Fixed number of type parametersVariadic TemplatesAccept an arbitrary number of type arguments (e.g., std::tuple<int, double, string>)
Compile-time type substitutionTemplate MetaprogrammingUse templates as a Turing-complete compile-time computation system for code generation and optimization

From introductory to advanced template features

As you progress through your C++ coursework, you will encounter these advanced features naturally. The key insight is that every advanced template technique is an extension of the basic mechanism covered in this lesson: define a parameterized pattern, let the compiler instantiate it for specific arguments. Whether you are constraining parameters with concepts, specializing for edge cases, or computing values at compile time with constexpr and template recursion, you are always building on the same foundational model of compile-time code generation from parameterized blueprints.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain why Stack<int> and Stack<double> are considered entirely different types by the C++ compiler, even though they originate from the same class template. What are the practical implications of this for assignment and function argument passing?
Show answer
Each instantiation of a class template produces a distinct, independent type in the C++ type system. Stack<int> and Stack<double> share no inheritance relationship and have different internal representations (the vector stores int vs. double). Practically, this means you cannot assign a Stack<int> to a Stack<double> without defining an explicit conversion. A function that accepts Stack<int> will not accept Stack<double>; you would need a function template to write type-generic functions over template instantiations.

Question 2: BASIC CALCULATION

Write a class template Box<T> that stores a single value of type T with a constructor, a getValue() accessor, and a setValue() mutator. Show how to instantiate it for both int and std::string.
Show answer
template <typename T> class Box { T value_; public: Box(const T& v) : value_(v) {} T getValue() const { return value_; } void setValue(const T& v) { value_ = v; } }; Instantiation: Box<int> intBox(42); creates a Box holding an integer. Box<std::string> strBox("hello"); creates a Box holding a string. The compiler generates two separate classes from this single template definition, each with fully type-checked operations.

Question 3: INTERMEDIATE

Consider a class template MinMax<T> that tracks the minimum and maximum of values inserted via an insert(const T&) method, and provides getMin() and getMax() accessors. What implicit requirements does this design place on the type T? Write the full implementation.
Show answer
The type T must support operator< (or operator>) and be copy-constructible. This is the 'implicit interface' of templates—the compiler will only check these requirements when the template is actually instantiated with a specific type. template <typename T> class MinMax { T min_, max_; bool initialized_ = false; public: void insert(const T& val) { if (!initialized_) { min_ = max_ = val; initialized_ = true; } else { if (val < min_) min_ = val; if (val > max_) max_ = val; } } T getMin() const { return min_; } T getMax() const { return max_; } }; If you instantiate MinMax<SomeType> where SomeType does not define operator<, the compiler will produce an error only when insert() is called.

Question 4: APPLIED

You are building a data logging system for a sensor network. Different sensors produce different data types (float for temperature, int for packet counts, std::string for status messages). Design a class template SensorLog<T> that stores up to N recent readings in a circular buffer (use a std::vector<T> with modular indexing). Provide record(const T&) and latest() methods. Discuss the design decision of making N a constructor parameter versus a template parameter.
Show answer
template <typename T> class SensorLog { std::vector<T> buffer_; size_t capacity_, head_ = 0, count_ = 0; public: explicit SensorLog(size_t cap) : buffer_(cap), capacity_(cap) {} void record(const T& reading) { buffer_[head_] = reading; head_ = (head_ + 1) % capacity_; if (count_ < capacity_) ++count_; } const T& latest() const { return buffer_[(head_ + capacity_ - 1) % capacity_]; } }; Making N a constructor parameter (as shown) allows different buffer sizes at runtime from the same class instantiation. Making it a non-type template parameter (template <typename T, size_t N>) would fix the size at compile time, potentially enabling stack allocation and better optimization, but SensorLog<float, 10> and SensorLog<float, 20> would be different types, reducing flexibility in heterogeneous collections.

Question 5: CRITICAL THINKING

A colleague argues that class templates violate the DRY (Don't Repeat Yourself) principle because the compiler generates a separate copy of the class for every type used, leading to code bloat. Another colleague counters that templates are the ultimate DRY tool because the programmer writes the logic only once. Analyze both positions. Under what circumstances could template-induced code bloat become a genuine concern, and what mitigation strategies exist?
Show answer
Both positions have merit, and the resolution depends on the distinction between source-level DRY and binary-level duplication. At the source level, templates are indeed the ultimate DRY mechanism: one definition serves all types, eliminating the need to maintain parallel implementations. At the binary level, each instantiation (Stack<int>, Stack<double>, etc.) generates distinct machine code, which can increase binary size—a phenomenon called 'template bloat.' This becomes a genuine concern in embedded systems with limited flash memory, or in large-scale projects with hundreds of instantiations. Mitigation strategies include: (1) factoring type-independent logic into a non-template base class (the 'thin template' idiom), (2) using explicit instantiation declarations (extern template) to control where instantiations occur, (3) designing templates to delegate to type-erased implementations where performance is not critical, and (4) relying on the linker's ability to deduplicate identical instantiations (COMDAT folding). Modern compilers and linkers handle this well in practice, and for most applications the compile-time type safety and zero-overhead abstraction far outweigh the binary size cost.

SUMMARY

Lesson Summary

Summary

A class template is a parameterized blueprint that the compiler uses to generate type-specific classes through template instantiation. You declare a template with the template <typename T> prefix, use T as a placeholder type throughout the class body, and instantiate it by specifying a concrete type in angle brackets (e.g., MyClass<int>). Each instantiation produces a distinct, independent type with full compile-time type safety and zero runtime overhead.

Key practical details include: defining template member functions with the ClassName<T>:: scope qualifier, placing the complete template definition in header files so the compiler has access to the full definition at every point of use, and understanding that only called member functions are instantiated. Class templates form the foundation of the Standard Template Library (STL) and are the gateway to advanced techniques including template specialization, variadic templates, and C++20 concepts.

Varsity Tutors • C++ • Class Templates — Use class templates (intro)