Skip to content

Templates

Templates are the foundation of generic programming in C++. They allow you to write generic functions or classes that work with any arbitrary data type, letting the compiler generate the concrete type-specific implementations at compile time.


1. Function Templates and Argument Deduction

Instead of writing identical logic for different data types (e.g. overloading a math helper for int, float, and double), you define a single Function Template.

#include <iostream>

// Declare the template parameter T
template <typename T>
T find_max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    // 1. Automatic Type Deduction
    int i = find_max(10, 20);       // Compiler deduces T as int
    double d = find_max(3.14, 2.71); // Compiler deduces T as double

    // 2. Explicit Type Specification
    // Forces conversion of parameters to double
    double mix = find_max<double>(10, 2.5); 

    return 0;
}

2. Class Templates and CTAD (C++17)

Class templates allow you to define generic data structures (like vectors, arrays, or boxes) that can contain any data type.

1
2
3
4
5
6
7
template <typename T>
class Wrapper {
    T data;
public:
    Wrapper(T val) : data(val) {}
    T get_value() const { return data; }
};

Class Template Argument Deduction (CTAD)

Before C++17, you always had to specify the template type when instantiating a class template:

Wrapper<int> wrap1(42); // Explicit (Pre-C++17 required)
In C++17, the compiler can automatically deduce the class template arguments from the constructor inputs:
Wrapper wrap2(42);      // Deduced as Wrapper<int>

3. Template Specialization

Sometimes, you need a different implementation for a specific type to optimize performance or fix logic issues. This is handled using Template Specialization.

1. Full Specialization

Provides a complete alternative definition for a single specific type.

#include <iostream>

// Primary (Generic) Template
template <typename T>
void print_type(T value) {
    std::cout << value << " is a generic value.\n";
}

// Full Specialization for bool
template <>
void print_type<bool>(bool value) {
    std::cout << (value ? "true" : "false") << " is a boolean value.\n";
}

2. Partial Specialization

Provides a specialized implementation for a subset of types (e.g., all pointer types).

#include <iostream>

template <typename T>
class Wrapper2 {
    T data;
public:
    Wrapper2(T val) : data(val) {}
};

// Specialization for all pointer types
template <typename T>
class Wrapper2<T*> {
    T* data;
public:
    Wrapper2(T* val) : data(val) {}
    void print_dereferenced() const {
        if (data) std::cout << *data << "\n";
    }
};

4. Non-Type Template Parameters

In addition to types (typename), template parameters can accept constant values of certain types (like integers, enums, or references/pointers).

This is widely used to create compile-time sized arrays or configurations.

#include <iostream>

// 'Limit' is a non-type template parameter
template <typename T, size_t Limit>
class StaticArray {
    T data[Limit]; // Size is allocated on the stack at compile-time
public:
    size_t size() const { return Limit; }
};

int main() {
    StaticArray<int, 256> buffer;
    std::cout << "Buffer capacity: " << buffer.size() << "\n"; // Prints 256
}

5. Why Templates Must Live in Headers

A common point of confusion is why template implementations are written inside header files (.hpp / .h) rather than separated into .cpp source files.

The Instantiation Mechanism

  1. Compilation Phase: Templates are blueprints, not concrete executable code. The compiler does not compile a template in isolation.
  2. Usage Phase: When you compile find_max(10, 20), the compiler must generate (instantiate) a function called find_max<int>.
  3. The Problem: To generate that concrete code, the compiler needs to see the full implementation of the template. If the implementation is in a separate .cpp file, the compiler only sees the declaration and leaves a reference for the linker.
  4. Linker Error: The linker will look for the compiled symbol find_max<int>, but since the .cpp template source was never instantiated during compiling (as it didn't know what types would be used), it fails with an undefined reference error.

Rule: Always write both template declarations and definitions in header files.


6. Variadic Templates and Fold Expressions

Introduced in C++11, Variadic Templates allow templates to accept an arbitrary number of arguments of any type. C++17 enhanced this with Fold Expressions, allowing you to perform operations on the argument pack using a concise syntax.

#include <iostream>

// C++17 Fold Expression: Sum all arguments recursively
template <typename... Args>
auto sum_all(Args... args) {
    return (args + ...); // Unpacks to: arg1 + arg2 + ... + argN
}

int main() {
    auto result = sum_all(1, 2.5, 3, 4.2); // Unpacks and sums as double
    std::cout << "Sum: " << result << "\n"; // Prints 10.7
    return 0;
}