Templates

Templates let you write a function or class once, with a placeholder for the type, and have the compiler generate a fully type-checked, type-specific version of it for every type you actually use — one of the biggest differences between C++ and the C it grew out of.

C has no built-in way to write one piece of code that works correctly and type-safely for many different types. A generic C swap function has to fall back on void* and an explicit element size, which throws away the type checker entirely — the compiler can no longer catch a mistake like swapping an int with a double by accident. C++ templates close that gap: a template is a blueprint, written once with a placeholder standing in for a real type, from which the compiler generates a fully concrete, fully type-checked version for every type it's actually used with. This page covers function templates and class templates, how template arguments are deduced from a call or declared explicitly, multiple and default template parameters, non-type template parameters, full and partial specialization, variadic templates and C++17 fold expressions, and what actually happens at compile time when a template gets instantiated.

One of the Biggest Differences from C

C's answer to "write this once, for any type" is either a macro (which has no type checking and can silently misbehave with unexpected operator precedence) or a function taking void* pointers plus an explicit element size, like the standard library's qsort. Both approaches throw away the compiler's ability to catch type mistakes at compile time.

c_swap works byte-by-byte through untyped pointers, so it doesn't know or care whether it's swapping ints, doubles, or two completely unrelated struct types passed in by mistake — the type system offers no protection at all.

A C++ function template achieves the same generality without giving up type safety: the compiler generates a separate, fully typed version of the function for each distinct type it's called with, and every one of those generated versions is checked exactly as strictly as a hand-written function would be.

Calling cpp_swap(x, y) with two ints generates a version that only ever operates on ints; calling it with two std::string values generates an entirely separate version for strings. If you tried to call cpp_swap on an int and a double, the compiler would reject it — T can't simultaneously mean both types in one call.

/* C: void* loses type information entirely */
void c_swap(void *a, void *b, size_t size)
{
    unsigned char *pa = (unsigned char *)a;
    unsigned char *pb = (unsigned char *)b;
    for (size_t i = 0; i < size; ++i)
    {
        unsigned char temp = pa[i];
        pa[i] = pb[i];
        pb[i] = temp;
    }
}
/* Nothing stops a caller from passing an int* and a double* here by mistake */
template <typename T>
void cpp_swap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

What Is a Template?

A template is not compiled code by itself — it's a pattern the compiler uses to generate real code once it sees what type(s) the template is actually used with. The template <typename T> line introduces T as a placeholder name for "some type, to be determined later," and everything that follows can use T anywhere an ordinary type name would go.

The same template body generates three distinct functions here — one for int, one for double, one for char — each produced automatically because my_max was called with arguments of that type. typename and class are interchangeable in this position; typename is the more common modern style.

#include <iostream>

template <typename T>
T my_max(T a, T b)
{
    return (a > b) ? a : b;
}

int main()
{
    std::cout << my_max(3, 7) << "\n";        // T = int
    std::cout << my_max(2.5, 1.1) << "\n";     // T = double
    std::cout << my_max('a', 'z') << "\n";     // T = char
    return 0;
}

Function Templates and Class Templates

my_max above is a function template. The same idea applies to whole classes: a class template lets an entire class be written once, generic over one or more types, and instantiated for each concrete type it's used with.

Box<int> and Box<std::string> are two entirely separate, independently compiled classes generated from the same template — Box<int>::contents is an int, Box<std::string>::contents is a std::string, and neither class knows the other exists. Unlike a function template, a class template's type argument (the <int> or <std::string>) must always be written explicitly; it's never deduced from a constructor call in versions of C++ before C++17's class template argument deduction.

template <typename T>
class Box
{
public:
    explicit Box(T value) : contents(value) {}

    T get() const { return contents; }
    void set(T value) { contents = value; }

private:
    T contents;
};

int main()
{
    Box<int> intBox(42);
    Box<std::string> stringBox("hello");

    std::cout << intBox.get() << "\n";       // 42
    std::cout << stringBox.get() << "\n";    // hello
    return 0;
}

Template Parameters and Argument Deduction

For function templates, the compiler usually figures out T on its own by looking at the types of the arguments actually passed in — this is called template argument deduction, and it's why my_max(3, 7) doesn't need to be written as my_max<int>(3, 7). Deduction can be overridden by supplying the type explicitly, which is sometimes necessary when the arguments alone don't pin down a single unambiguous type.

The commented-out call fails because the two arguments have different types and the compiler refuses to silently pick one — deduction requires every use of the same template parameter within one call to agree on a single type, unless you resolve the mismatch by casting one argument or specifying T explicitly.

template <typename T>
T my_max(T a, T b)
{
    return (a > b) ? a : b;
}

int main()
{
    my_max(3, 7);          // T deduced as int
    my_max<double>(3, 7);  // T explicitly forced to double, even though 3 and 7 are int literals
    // my_max(3, 7.5);     // error: T can't be deduced as both int and double at once
    return 0;
}

Multiple Template Parameters and Default Template Arguments

A template can take more than one type parameter, letting the two (or more) generic types differ from each other entirely, and any trailing template parameter can be given a default, just like a default function argument.

Pair<int, std::string> uses both type parameters explicitly and lets them differ, while Pair<double> only supplies T and lets U fall back to its default, which is T itself here — so b is really a Pair<double, double>.

template <typename T, typename U = T>
class Pair
{
public:
    Pair(T first, U second) : first(first), second(second) {}

    T first;
    U second;
};

int main()
{
    Pair<int, std::string> a(1, "one");   // T = int, U = string
    Pair<double> b(3.14, 2.71);            // U defaults to T, so this is Pair<double, double>
    return 0;
}

Non-Type Template Parameters

A template parameter doesn't have to be a type at all — it can be a compile-time constant value, most commonly an integer, which is called a non-type template parameter. This is how a fixed-size array type can bake its size into the type itself, at compile time, without any runtime storage overhead for that size.

N here isn't a type — it's a value, fixed at compile time as part of the type itself. FixedArray<int, 5> and FixedArray<int, 10> are two different types with two different sizes, exactly the mechanism std::array<T, N> in the standard library is built on.

template <typename T, std::size_t N>
class FixedArray
{
public:
    T &operator[](std::size_t index) { return data[index]; }
    std::size_t size() const { return N; }

private:
    T data[N];
};

int main()
{
    FixedArray<int, 5> arr;
    for (std::size_t i = 0; i < arr.size(); ++i)
        arr[i] = static_cast<int>(i * i);
    return 0;
}

Template Specialization and Partial Specialization

Sometimes the generic template body is wrong, or merely suboptimal, for one particular type. Full specialization lets you write a completely separate implementation for one exact type, while the general template still covers everything else.

template <> Describer<bool> { ... } isn't a new template at all — it's a completely separate, hand-written definition that the compiler uses instead of instantiating the generic body whenever T is exactly bool. Every other type still goes through the general template.

Partial specialization narrows the general template for a family of types that share some pattern — most commonly, any pointer type — without pinning down one single exact type.

Box<T*> still has its own template parameter T, so it matches Box<int*>, Box<double*>, Box<char*>, and every other pointer type at once — it's more specific than the fully generic Box<T> but less specific than a full specialization pinned to one exact type.

template <typename T>
class Describer
{
public:
    static std::string describe() { return "a value of some type"; }
};

// Full specialization: bool gets its own description
template <>
class Describer<bool>
{
public:
    static std::string describe() { return "a boolean, true or false"; }
};

std::cout << Describer<int>::describe() << "\n";   // a value of some type
std::cout << Describer<bool>::describe() << "\n";  // a boolean, true or false
template <typename T>
class Box
{
public:
    static std::string kind() { return "regular value"; }
};

// Partial specialization: matches Box<T*> for any T
template <typename T>
class Box<T *>
{
public:
    static std::string kind() { return "pointer"; }
};

std::cout << Box<int>::kind() << "\n";    // regular value
std::cout << Box<int *>::kind() << "\n";  // pointer

Variadic Templates, Parameter Packs, and Fold Expressions

A variadic template accepts any number of template arguments — zero, one, or many — using a parameter pack, written with an ellipsis: typename... Args. The classic way to process a pack is recursively: handle the first argument, then recurse on the rest.

Each call to the templated print peels off First and forwards the remaining Rest... pack to a recursive call, until the pack is empty and the zero-argument overload of print() stops the recursion.

C++17 introduced fold expressions, which let many common parameter-pack operations — like summing every argument or applying an operator across all of them — be written directly, without hand-rolled recursion.

(args + ...) is a unary right fold: the compiler expands it into args1 + (args2 + (args3 + ... + argsN)) for however many arguments were actually passed, entirely at compile time — there's no runtime loop or recursive function call generated for this version.

#include <iostream>

void print()
{
    std::cout << "\n";   // base case: no arguments left
}

template <typename First, typename... Rest>
void print(First first, Rest... rest)
{
    std::cout << first << " ";
    print(rest...);   // recurse with one fewer argument
}

int main()
{
    print(1, 2.5, "three", 'f');
    return 0;
}
template <typename... Args>
auto sum(Args... args)
{
    return (args + ...);   // fold expression: args1 + (args2 + (args3 + ...))
}

int main()
{
    std::cout << sum(1, 2, 3, 4) << "\n";        // 10
    std::cout << sum(1.5, 2.5, 3.0) << "\n";     // 7
    return 0;
}

Template Instantiation

A template by itself generates no code — the compiler only produces a real function or class the moment the template is instantiated, meaning used with a specific set of template arguments somewhere in the program. Calling my_max(3, 7) and my_max(2.5, 1.1) causes two separate instantiations, my_max<int> and my_max<double>, each compiled and type-checked independently.

Before this call, no version of square exists in the compiled program at all — it's the act of calling square(5) that triggers the compiler to generate square<int> specifically.

This instantiate-on-use behavior is exactly why template definitions almost always live in header files rather than being split into a .h declaration and a .cpp implementation the way ordinary functions are: the compiler needs to see the full template body in every translation unit that instantiates it, and a .cpp file compiled on its own has no idea which specific types the template will eventually be used with.

Why does C's generic swap function have to accept void* pointers, and what does that cost in terms of type safety compared to a C++ template version?

C has no mechanism to write one function body that the compiler specializes per type, so a generic C swap has to operate on raw, untyped memory through void* plus an explicit byte size — which means the compiler can no longer check that the two things being swapped are even the same type. A C++ function template instead generates a distinct, fully type-checked version of the function for each type it's actually called with, so mismatched types are caught at compile time.

What is template argument deduction, and when does it fail?

Template argument deduction is the compiler's ability to figure out a function template's type parameter(s) automatically from the types of the arguments passed in a call, without the caller writing them explicitly. It fails (or gives a compile error) when the same template parameter would have to simultaneously mean two different types within one call — for example calling my_max(3, 7.5) where T would need to be both int and double at once.

What is the difference between full template specialization and partial template specialization?

Full specialization provides a completely separate implementation for one exact, specific type (like Describer<bool>), replacing the general template entirely for that type. Partial specialization narrows the general template for a whole family of types that share a pattern, most commonly any pointer type (Box<T*>), while still keeping its own template parameter and matching many types at once rather than one.

What does a non-type template parameter let you express that a regular type template parameter cannot?

A non-type template parameter is a compile-time constant value (typically an integer) rather than a type, letting that value become part of the type itself. This is how a fixed-size container type like FixedArray<int, 5> can bake its capacity into the type at compile time, so FixedArray<int, 5> and FixedArray<int, 10> are distinct types with no runtime storage needed just to remember the size.

Why does a template's full definition usually need to live in a header file rather than a separate .cpp file, and what error shows up when that rule is broken?

A template produces no compiled code until it's instantiated for a specific set of types, and instantiation requires the compiler to see the entire template body at the point of use. If the definition lives only in a .cpp file, other translation units that call the template have only its declaration, so the compiler never generates the needed instantiation anywhere — the usual symptom is a linker error, undefined reference to the specific instantiation, not a compile error.

Write a function template greaterOf(T a, T b) that returns the larger of two values of any comparable type, and demonstrate it with int, double, and std::string arguments.

greaterOf is a single template body, but the compiler generates three distinct instantiations here — greaterOf<int>, greaterOf<double>, and greaterOf<std::string> — one per call, each relying on that type's own operator> (built-in comparison for int and double, lexicographic comparison for std::string).

Write a class template Stack<T> with push, pop, top, and empty operations backed by a std::vector<T>, and demonstrate it holding ints in one instance and strings in another.

Stack<int> and Stack<std::string> are two separate classes generated from the same template, each wrapping its own std::vector<T> for the appropriate T. All the push/pop/top logic is written exactly once, yet works correctly and type-safely for both an int stack and a string stack.

What are the actual types of p1.b and p2.b, and why do they differ even though neither Pair<int> nor Pair<int, double> explicitly wrote out both type arguments the same way?

p1.b is an int, and p2.b is a double. Pair<int> supplies only T (as int) and lets U fall back to its default, which is T itself — so Pair<int> is really Pair<int, int>. Pair<int, double> supplies both type arguments explicitly, overriding the default entirely, so U becomes double instead of matching T.

Write a variadic template function countArgs(Args... args) that returns how many arguments it was called with, using sizeof...(Args), and demonstrate it with calls of different argument counts.

sizeof...(Args) is a compile-time operator built specifically for parameter packs — it evaluates to the number of types in the pack, entirely at compile time, without inspecting the actual argument values at all. That's why countArgs works correctly and consistently for zero, one, or any number of arguments of any types.

template <typename T>
T square(T x)
{
    return x * x;
}

// square<int> is instantiated here, because square is called with an int
int result = square(5);
template <typename T, typename U = T>
struct Pair
{
    T a;
    U b;
};

int main()
{
    Pair<int> p1{1, 2};
    Pair<int, double> p2{1, 2.5};
    return 0;
}