C++ Primer, Chapter 16

Templates & Generic Programming

Function templates, class templates, specialization, and variadic templates. The compile-time machinery that powers the entire standard library.

Prerequisites: Chapter 13 (Copy Control) + Chapter 15 (OOP).
10
Chapters
4+
Simulations

Chapter 0: Why Templates?

You write a compare function for int. Then you need one for double. Then for string. The logic is identical every time — only the type changes. Do you copy-paste three times?

Templates let you write the logic once, with the type as a parameter. The compiler generates the type-specific code for you.

c++
// Without templates: three nearly identical functions
int compare(int a, int b) { if (a < b) return -1; if (b < a) return 1; return 0; }
double compare(double a, double b) { /* same */ }
int compare(string a, string b) { /* same */ }

// With templates: one definition covers all types
template <typename T>
int compare(const T &a, const T &b) {
    if (a < b) return -1;
    if (b < a) return 1;
    return 0;
}
Templates vs OOP: Both provide polymorphism, but at different times. OOP (virtual functions) resolves at runtime — you pay for a vtable lookup. Templates resolve at compile time — zero runtime overhead, but the compiler generates code for every type used.
Template Instantiation

Click a type to "instantiate" the template. The compiler generates a new function for each type used.

What is the main advantage of templates over manually writing type-specific functions?

Chapter 1: Function Templates

A function template is a formula for generating type-specific functions. The compiler deduces the template arguments from the function call:

c++
template <typename T>
T mymax(const T &a, const T &b) {
    return a < b ? b : a;
}

mymax(3, 7);          // T deduced as int → mymax<int>
mymax(3.14, 2.72);    // T deduced as double → mymax<double>
mymax(string("hi"), string("bye"));  // T = string

// Explicit template argument when deduction fails
mymax<double>(3, 3.14);  // force T = double

Instantiation

When the compiler sees mymax(3, 7), it instantiates the template: generates a real function mymax<int> with T replaced by int. Each distinct type produces a separate instantiation.

Templates are not compiled until instantiated. The template definition itself is just a pattern. Errors in the template body might not be detected until you actually use the template with a specific type. This makes template error messages notoriously hard to read.

Template Requirements

The compare template uses < on type T. If you instantiate it with a type that doesn't have operator<, you get a compile error (at instantiation time, not definition time). Templates implicitly require certain operations from their type parameters.

When is a function template actually compiled into real code?

Chapter 2: Class Templates

A class template is a blueprint for generating classes. Unlike function templates, the compiler cannot deduce template arguments for class templates — you must specify them explicitly:

c++
template <typename T>
class Blob {
public:
    typedef T value_type;
    Blob() : data(make_shared<vector<T>>()) {}
    Blob(initializer_list<T> il)
        : data(make_shared<vector<T>>(il)) {}

    void push_back(const T &t) { data->push_back(t); }
    void push_back(T &&t) { data->push_back(std::move(t)); }

    T& back() { return data->back(); }
    T& operator[](size_t i) { return (*data)[i]; }
    size_t size() const { return data->size(); }
private:
    shared_ptr<vector<T>> data;
};

Blob<int> ia = {0, 1, 2, 3};
Blob<string> sa = {"hi", "bye"};
Each instantiation is a distinct class. Blob<int> and Blob<string> are completely separate types with no relationship to each other. The compiler generates the entire class for each distinct template argument.

Member Functions

Member functions of class templates are themselves function templates. They are only instantiated when used — if you never call push_back, the compiler never generates code for it. This means a class template can be instantiated with a type that supports some but not all operations.

Template Type Aliases

c++
// C++11: template aliases with using
template <typename T>
using twin = pair<T, T>;

twin<int> p;       // pair<int, int>
twin<string> s;    // pair<string, string>
Are Blob<int> and Blob<string> the same type?

Chapter 3: Template Parameters

Template parameters can be types (typename T) or values (int N). They can also have defaults:

c++
// Type parameter
template <typename T>
void print(const T &val) { cout << val; }

// Non-type parameter (must be a constant expression)
template <unsigned N, unsigned M>
int compare(const char (&a)[N], const char (&b)[M]) {
    return strcmp(a, b);
}
compare("hi", "world");  // N=3, M=6 (includes null terminator)

// Default template arguments
template <typename T, typename F = less<T>>
int compare(const T &a, const T &b, F f = F()) {
    if (f(a, b)) return -1;
    if (f(b, a)) return 1;
    return 0;
}

Member Templates

A class (template or not) can have member functions that are themselves templates:

c++
template <typename T>
class Blob {
    // Member template: construct from any iterator range
    template <typename It>
    Blob(It b, It e) : data(make_shared<vector<T>>(b, e)) {}
};
typename vs class: In template parameter lists, typename and class are interchangeable. By convention, typename is preferred because it makes clear the parameter can be any type, not just a class.
What constraint applies to non-type template parameters?

Chapter 4: Type Deduction

When calling a function template, the compiler deduces the template arguments from the function arguments. The deduction rules are precise:

Function ParameterArgumentDeduced T
const T&int iint (reference & const stripped)
const T&const int ciint
T&int iint
T (by value)const int ciint (top-level const dropped)
T&&lvalue int iint& (reference collapsing!)
T&&rvalue 42int
Very limited conversions during deduction. The compiler will not apply normal conversions (like int to double) when deducing template arguments. compare(3, 3.14) is an error because T can't be both int and double. You must either cast or provide explicit template arguments.

Trailing Return Types

When the return type depends on the template parameters, use a trailing return type with decltype:

c++
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg) {
    return *beg;  // returns a reference to the element
}
Why does compare(3, 3.14) fail for template<typename T> int compare(const T&, const T&)?

Chapter 5: std::move & Perfect Forwarding

Two of the most important utilities in modern C++ — std::move and std::forward — are implemented using template mechanics.

How std::move Works

c++
// Simplified std::move implementation
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
    return static_cast<typename remove_reference<T>::type&&>(t);
}

It's just a cast. std::move doesn't move anything — it casts its argument to an rvalue reference, which allows the move constructor/assignment to be called.

Perfect Forwarding

Forwarding means passing arguments to another function preserving their lvalue/rvalue nature and const/non-const qualifiers:

c++
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2) {
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}
// If t1 was an lvalue, forward preserves it as lvalue
// If t1 was an rvalue, forward preserves it as rvalue
T&& in templates is special. When T is a template parameter, T&& is a forwarding reference (also called universal reference), not an rvalue reference. It can bind to both lvalues and rvalues through reference collapsing rules. This is how perfect forwarding works.
Reference CollapsingResult
T& &T&
T& &&T&
T&& &T&
T&& &&T&&
Rule of thumb: lvalue reference to anything is an lvalue reference. Only rvalue-ref to rvalue-ref gives rvalue reference. Lvalues are "sticky."
What is a "forwarding reference" (universal reference)?

Chapter 6: Variadic Templates

A variadic template takes a variable number of template parameters, called a parameter pack:

c++
// Base case: one argument
template <typename T>
ostream& print(ostream &os, const T &t) {
    return os << t;
}

// Recursive case: peel off first, recurse on rest
template <typename T, typename... Args>
ostream& print(ostream &os, const T &t, const Args&... rest) {
    os << t << ", ";
    return print(os, rest...);  // expand pack: recurse
}

print(cout, 42, "hello", 3.14);
// → print(cout, 42, "hello", 3.14)  T=int,    Args={char*,double}
// → print(cout, "hello", 3.14)       T=char*,  Args={double}
// → print(cout, 3.14)                T=double, base case
The pattern is always the same: Peel the first element, process it, recurse on the rest. The base case handles the last element. This is functional programming in C++ templates.

sizeof... Operator

c++
template <typename... Args>
void foo(Args... args) {
    cout << sizeof...(Args) << endl;  // number of type params
    cout << sizeof...(args) << endl;  // number of function args
}

Forwarding Parameter Packs

Combine variadic templates with perfect forwarding for maximum generality. This is how make_shared and emplace_back work:

c++
template <typename... Args>
void emplace_back(Args&&... args) {
    // forwards each argument preserving lvalue/rvalue
    alloc.construct(first_free++, std::forward<Args>(args)...);
}
How does a variadic template process its arguments?

Chapter 7: Template Specialization

Sometimes the generic template doesn't work (or isn't optimal) for a specific type. A specialization provides a custom implementation for that type:

c++
// Primary template
template <typename T>
int compare(const T &a, const T &b) {
    if (a < b) return -1;
    if (b < a) return 1;
    return 0;
}

// Full specialization for const char*
template <>  // empty angle brackets = specialization
int compare(const char* const &a, const char* const &b) {
    return strcmp(a, b);  // compare strings, not pointer values
}
Why specialize for const char*? The primary template would compare pointer values (memory addresses), not the strings they point to. The specialization calls strcmp to do the right thing. Without specialization, compare("hi", "bye") would give meaningless results.

Class Template Partial Specialization

Class templates (but not function templates) can be partially specialized — specialized for a subset of template arguments:

c++
// Primary template
template <typename T> struct remove_reference       { using type = T; };

// Partial specialization for lvalue references
template <typename T> struct remove_reference<T&>   { using type = T; };

// Partial specialization for rvalue references
template <typename T> struct remove_reference<T&&>  { using type = T; };
This is how type traits work. The standard library header <type_traits> is full of partially specialized templates that manipulate types at compile time: remove_reference, add_const, is_pointer, enable_if. They're the foundation of template metaprogramming.
What does template <> (empty angle brackets) signify before a function definition?

Chapter 8: Instantiation Simulator

Let's visualize how the compiler processes templates. When you write a template call, the compiler substitutes types and generates real code. Each unique type combination produces a separate instantiation.

Template Instantiation Pipeline

Select a template and provide type arguments. Watch the compiler substitute types and generate code.

Observe: Each unique instantiation produces entirely separate code. compare<int> and compare<string> are two different functions. This is called code bloat in the worst case — but the compiler can optimize and inline aggressively because it knows the exact types.

Chapter 9: Beyond — Connections

Templates are the foundation of the entire C++ standard library. vector, shared_ptr, sort, find, make_shared — all are templates. Understanding templates is understanding how C++ works at its core.

This ChapterBuilds Toward
Function templatesGeneric algorithms (Ch 10), standard library
Class templatesContainer design (vector, map, etc.)
Type deductionauto, decltype, lambda type inference
Perfect forwardingemplace_back, factory functions, make_unique
Variadic templatestuple, variant, structured bindings (C++17)
SpecializationType traits, SFINAE, concepts (C++20)
Lippman's insight: "Templates are the foundation of generic programming in C++. OOP and generic programming both deal with types that are not known at the time the program is written. The distinction is: OOP deals with types that are not known until runtime; templates deal with types not known until compile time." Both are essential tools in the C++ programmer's toolkit.

The Complete Series

You've now covered the essential chapters of C++ Primer — from basic types through templates. Return to the chapter index to review any chapter.