Skip to content
C++
C++98 / C++20Intermediate

Function Templates — Generic Algorithms

A function template is a blueprint for generating functions. Rather than writing separate overloads of max for int, double, std::string, and every other comparable type, you write one template and the compiler generates the right version for each type it sees used. The code you write is compiled once as a template definition; concrete functions (called instantiations) are generated on demand at each unique call site. This is what makes the entire C++ standard library — std::sort, std::vector, algorithms — type-generic without runtime overhead.

Writing a function template

A function template begins with template<typename T> (or template<class T> — identical meaning). T is a type parameter: a placeholder that the compiler replaces with a real type at each call site.

// One template, works for any type that supports operator<
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// Compiler generates separate functions for each type:
int    i = max(3, 7);          // generates: int max(int, int)
double d = max(3.14, 2.72);    // generates: double max(double, double)
auto   s = max(std::string("apple"), std::string("banana")); // string max

The generated code is identical to what you would have written by hand for each type. There is no virtual dispatch, no type erasure, no boxing — just machine code that operates directly on the concrete type.

Template argument deduction

In most cases, the compiler can deduce the template argument from the function arguments — you don't need to write the type explicitly. When deduction is ambiguous or impossible (e.g., for return type only, or when arguments have different types), you must provide the template argument explicitly.

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

// Deduced:
max(1, 2);           // T = int (deduced from both args)
max(1.0, 2.0);       // T = double

// FAILS deduction — args have different types:
// max(1, 2.0);      // ERROR: T can't be both int and double at once

// Explicit template argument:
max<double>(1, 2.0); // OK: int is implicitly converted to double

// Return type that can't be deduced:
template<typename To, typename From>
To narrow_cast(From f) { return static_cast<To>(f); }

narrow_cast<int>(3.9);  // must specify To; From deduced from arg

Multiple type parameters

Templates can have multiple type parameters. This is useful for functions that work on two independently typed values, or for return types that are different from the parameter types.

// Two independent type parameters:
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {   // deduced return type (C++11)
    return a + b;
}

int i = 3;
double d = 1.5;
auto result = add(i, d);   // T=int, U=double, result is double

// C++14 and later: plain auto is enough:
template<typename T, typename U>
auto add(T a, U b) {
    return a + b;  // return type deduced from the expression
}

Non-type template parameters

Template parameters don't have to be types — they can be compile-time constant values (integers, enums, pointers, etc.). Non-type template parameters are the mechanism behind std::array<T, N>— the size N is embedded at compile time with no runtime storage.

template<typename T, std::size_t N>
T sum(const T (&arr)[N]) {   // N deduced from array size
    T result{};
    for (const T& x : arr) result += x;
    return result;
}

int nums[] = {1, 2, 3, 4, 5};
std::cout << sum(nums);   // T=int, N=5 → 15

// Compile-time fixed-size array:
template<std::size_t N>
void printBits(unsigned int value) {
    for (int i = N-1; i >= 0; --i)
        std::cout << ((value >> i) & 1);
}
printBits<8>(42);   // "00101010"

Constraining templates (C++20 requires)

Without constraints, an unconstrained template will accept any type and produce an error only when the substituted type doesn't support the required operations — often resulting in a wall of cryptic error messages. C++20 concepts and requires clauses let you state requirements explicitly, producing clean, readable errors at the call site.

#include <concepts>

// Unconstrained — compiles for any T, error only at instantiation:
template<typename T>
T max_bad(T a, T b) { return (a > b) ? a : b; }

// Constrained with std::totally_ordered concept (C++20):
template<std::totally_ordered T>
T max_good(T a, T b) { return (a > b) ? a : b; }

// Or with a requires clause:
template<typename T>
    requires std::totally_ordered<T>
T max_also_good(T a, T b) { return (a > b) ? a : b; }

// Even shorter with abbreviated function templates (C++20):
auto max_short(std::totally_ordered auto a, std::totally_ordered auto b) {
    return (a > b) ? a : b;
}

Key rules to remember

Function templates generate separate code for each unique combination of template arguments

max<int> and max<double> are completely independent functions in the binary. Templates are a compile-time mechanism — zero runtime cost.

Template definitions must be visible where they are instantiated — put them in headers

Unlike regular functions, you cannot put the template definition in a .cpp file and the declaration in a header. Both must be in the header.

Prefer explicit constraints (C++20 concepts) over unconstrained templates

Unconstrained templates produce cascading error messages inside the template body. Constraints catch misuse at the call site with a clear message.

Let the compiler deduce template arguments — only specify them when needed

max(3, 7) is cleaner than max<int>(3, 7). Explicit arguments are only needed when deduction fails or is ambiguous.

Sign in to track progress