Docs

README

Function Templates in C++

Table of Contents

  1. What are Templates
  2. How Template Instantiation Works
  3. Basic Syntax
  4. Type Deduction
  5. Multiple Type Parameters
  6. Non-Type Parameters
  7. Template Specialization
  8. Best Practices

What are Templates

Templates enable generic programming - write code once, use with any type. The compiler generates specific versions for each type you use.

The Problem Templates Solve

┌─────────────────────────────────────────────────────────────────────────────┐
│                       WITHOUT TEMPLATES                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  You need a max function for different types...                             │
│                                                                              │
│  int maxInt(int a, int b) { return a > b ? a : b; }                        │
│  double maxDouble(double a, double b) { return a > b ? a : b; }            │
│  float maxFloat(float a, float b) { return a > b ? a : b; }                │
│  long maxLong(long a, long b) { return a > b ? a : b; }                    │
│  string maxString(string a, string b) { return a > b ? a : b; }            │
│                                                                              │
│  ❌ Repetitive code!                                                        │
│  ❌ Easy to introduce bugs                                                  │
│  ❌ Must add new function for each type                                     │
│                                                                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                        WITH TEMPLATES                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  template<typename T>                                                        │
│  T max(T a, T b) { return a > b ? a : b; }                                  │
│                                                                              │
│  max(1, 2);           // Works with int                                     │
│  max(1.5, 2.5);       // Works with double                                  │
│  max('a', 'z');       // Works with char                                    │
│  max(str1, str2);     // Works with string                                  │
│                                                                              │
│  ✅ Write once, works with ANY type that supports >                         │
│  ✅ Type-safe (compiler catches mismatches)                                 │
│  ✅ No runtime overhead (resolved at compile time)                          │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

How Template Instantiation Works

When you use a template with a specific type, the compiler generates actual code for that type. This is called template instantiation.

Instantiation Process Visualization

┌─────────────────────────────────────────────────────────────────────────────┐
│                    TEMPLATE INSTANTIATION PROCESS                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  SOURCE CODE (what you write):                                               │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  template<typename T>                                                │    │
│  │  T square(T x) { return x * x; }                                     │    │
│  │                                                                      │    │
│  │  int main() {                                                        │    │
│  │      square(5);       // Use with int                                │    │
│  │      square(3.14);    // Use with double                             │    │
│  │      square(2.5f);    // Use with float                              │    │
│  │  }                                                                   │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                    │                                         │
│                                    ▼                                         │
│                           COMPILER SEES USAGES                               │
│                           and generates code:                                │
│                                    │                                         │
│          ┌─────────────────────────┼─────────────────────────┐              │
│          │                         │                         │              │
│          ▼                         ▼                         ▼              │
│  ┌──────────────┐         ┌──────────────┐         ┌──────────────┐        │
│  │ T = int      │         │ T = double   │         │ T = float    │        │
│  │              │         │              │         │              │        │
│  │ int square   │         │double square │         │float square  │        │
│  │ (int x) {    │         │(double x) {  │         │(float x) {   │        │
│  │   return x*x;│         │  return x*x; │         │  return x*x; │        │
│  │ }            │         │}             │         │}             │        │
│  └──────────────┘         └──────────────┘         └──────────────┘        │
│                                                                              │
│  COMPILED BINARY contains ALL THREE versions                                 │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Key Points About Instantiation

AspectDescription
WhenAt compile time, when template is used with a specific type
WhereOnly for types actually used in your code
Code Bloat?Each instantiation creates new code, but only for used types
Header FilesTemplates must be defined in headers (not .cpp files)

Basic Syntax

template<typename T>  // or template<class T>
T functionName(T param) {
    // T is the placeholder type
    return param;
}

Examples

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

template<typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

template<typename T>
void print(const vector<T>& vec) {
    for (const T& item : vec) {
        cout << item << " ";
    }
}

Type Deduction

Compiler deduces T from arguments:

template<typename T>
T add(T a, T b) { return a + b; }

add(1, 2);       // T deduced as int
add(1.5, 2.5);   // T deduced as double

// Explicit specification
add<long>(1, 2); // Force T = long

Deduction Conflicts

template<typename T>
T add(T a, T b) { return a + b; }

// add(1, 2.5);  // ERROR: T = int or double?

// Solutions:
add<double>(1, 2.5);  // Explicit
add(1.0, 2.5);        // Make types match

Multiple Type Parameters

template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

// C++14 simplified
template<typename T, typename U>
auto add(T a, U b) {
    return a + b;
}

add(1, 2.5);     // int + double = double
add(1.5, 2);     // double + int = double

Return Type Issues

template<typename T, typename U>
T badAdd(T a, U b) { return a + b; }  // Truncates if U > T

template<typename T, typename U>
auto goodAdd(T a, U b) { return a + b; }  // Correct return type

Non-Type Parameters

template<typename T, int Size>
class Array {
    T data[Size];
public:
    int size() const { return Size; }
};

Array<int, 10> arr;  // Fixed size at compile time

// Function with non-type parameter
template<int N>
int multiply(int x) {
    return x * N;
}

multiply<5>(10);  // Returns 50

Template Specialization

Override template for specific types:

// Primary template
template<typename T>
T absolute(T x) {
    return x < 0 ? -x : x;
}

// Specialization for bool
template<>
bool absolute<bool>(bool x) {
    return x;  // No negation for bool
}

// Partial specialization (for pointers)
template<typename T>
T* absolute(T* ptr) {
    return ptr;  // Just return pointer
}

Why Specialize?

// Generic print
template<typename T>
void print(const T& value) {
    cout << value;
}

// Specialize for C-strings
template<>
void print<const char*>(const char* const& value) {
    cout << "\"" << value << "\"";
}

// Specialize for bool
template<>
void print<bool>(const bool& value) {
    cout << (value ? "true" : "false");
}

Best Practices

✅ Do

// 1. Use auto for return types (C++14)
template<typename T, typename U>
auto add(T a, U b) { return a + b; }

// 2. Use const& for non-modified parameters
template<typename T>
void process(const T& item);

// 3. Use forwarding references for perfect forwarding
template<typename T>
void forward(T&& arg);

// 4. Define in headers
// Templates must be visible at instantiation

❌ Don't

// 1. Don't assume operators exist
template<typename T>
T multiply(T a, T b) { return a * b; }  // T must support *

// 2. Don't separate declaration/definition
// template.h - declaration only - WON'T WORK
// template.cpp - definition - WON'T LINK

Quick Reference

// Basic template
template<typename T>
T func(T param);

// Multiple types
template<typename T, typename U>
auto func(T a, U b);

// Non-type parameter
template<typename T, int N>
void func(T (&arr)[N]);

// Specialization
template<>
void func<int>(int param);

// Default template argument
template<typename T = int>
T func();

Compile & Run

g++ -std=c++17 -Wall examples.cpp -o examples && ./examples
README - C++ Tutorial | DeepML