Skip to content

Function Templates — Template Parameters, Type Deduction, Overloading

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Function Templates. We cover key concepts, practical examples, and best practices to help you master this topic.

C++ function templates allow writing a single function definition that works with any data type, with the compiler instantiating type-specific versions automatically at compile time.

What You'll Learn

You will write function templates with template type parameters and non-type parameters, understand template argument deduction and explicit instantiation, overload templates with regular functions and other templates, separate template declarations from definitions, and apply SFINAE-friendly patterns in overload resolution.

Why It Matters

Without templates, you would need to write separate overloads for every type: int max(int, int), double max(double, double), string max(string, string). A single function template replaces them all while preserving type safety. Templates are the foundation of generic programming in C++, used throughout the standard library and in every modern C++ codebase.

Learning Path

graph LR
    A["41: Iterator Types"] --> B["42: Function Templates"]
    B --> C["43: Class Templates"]
    C --> D["44: Template Specialization"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff

Basic Function Template

A function template starts with the template keyword followed by template parameters in angle brackets.

#include <iostream>
#include <string>

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

int main() {
    // Compiler deduces T = int
    std::cout << max(3, 7) << "\n";          // 7

    // T = double
    std::cout << max(3.14, 2.72) << "\n";    // 3.14

    // T = std::string
    std::cout << max(std::string("apple"), std::string("orange")) << "\n";  // orange

    // Explicit type specification
    std::cout << max<double>(3, 7.5) << "\n"; // 7.5
}

The compiler generates three separate functions: one for int, one for double, and one for std::string. Each is type-specific with zero runtime overhead.

Multiple Template Parameters

You can have multiple type parameters and mix type with non-type parameters.

#include <iostream>
#include <type_traits>

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

// Non-type template parameter (value parameter)
template <typename T, int Size>
T multiplyBySize(T value) {
    return value * Size;
}

int main() {
    auto result = add(3, 4.5);
    std::cout << result << "\n";  // 7.5

    std::cout << multiplyBySize<int, 10>(5) << "\n";   // 50
    std::cout << multiplyBySize<double, 3>(2.5) << "\n"; // 7.5
}

Non-type parameters must be compile-time constants: integers, enumerations, pointers, references, or std::nullptr_t.

Template Argument Deduction

The compiler deduces template arguments from the function arguments. Understanding deduction rules prevents surprises.

#include <iostream>
#include <string>

template <typename T>
void deduce(T a, T b) {
    std::cout << a << " " << b << "\n";
}

// Reference deduction preserves reference-ness
template <typename T>
void ref_deduce(T& a) {
    // T is deduced without reference
}

// Forwarding reference (T&&) is special
template <typename T>
void forward_deduce(T&& a) {
    // T = int& for lvalue, int for rvalue
}

int main() {
    deduce(1, 2);           // OK, T = int
    // deduce(1, 2.5);      // Error: conflicting types (int vs double)

    int x = 5;
    ref_deduce(x);           // T = int (not int&)

    forward_deduce(5);       // T = int
    forward_deduce(x);       // T = int& (special forwarding reference case)
}

Overloading with Templates

Templates participate in overload resolution with regular functions and other templates.

#include <iostream>
#include <cstring>

// 1. Regular function
void print(const char* s) {
    std::cout << "char*: " << s << "\n";
}

// 2. Template function
template <typename T>
void print(const T& value) {
    std::cout << "template: " << value << "\n";
}

// 3. More specialized template
template <typename T>
void print(T* ptr) {
    std::cout << "pointer template: " << *ptr << "\n";
}

int main() {
    print("hello");            // Exact match: calls #1 (char*)
    print(42);                 // Calls #2 (template, T = int)
    
    int x = 10;
    print(&x);                 // Calls #3 (pointer template, T = int)
    
    const char* msg = "world";
    print(msg);                // Calls #1 (exact match beats template)
}

Overload resolution prefers non-template functions over templates when both are equally good matches. Among templates, more specialized ones are preferred.

Separating Declaration and Definition

For function templates, the full definition must be visible at the point of instantiation. The common approach is to put everything in a header file.

// In header file (max.h)
#ifndef MAX_H
#define MAX_H

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

// Explicit instantiation declaration (C++11)
extern template int max(int, int);

#endif

// In one translation unit (max.cpp)
template int max(int, int);  // explicit instantiation definition

Without explicit instantiation, the compiler instantiates templates in every translation unit that uses them, potentially increasing compile times and binary size.

Common Mistakes

Mistake 1: Putting template definitions in .cpp files

// header.h
template <typename T>
T max(T a, T b);  // declaration only

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

The linker cannot find the definition when another file instantiates the template. Always keep definitions in headers.

Mistake 2: Assuming type conversion

template <typename T>
T max(T a, T b);

max(3, 7.5);  // Error: conflicting deduction (int vs double)

Use explicit arguments: max<double>(3, 7.5) or make the template accept two types.

Mistake 3: Forgetting typename for dependent types

template <typename T>
void example() {
    T::iterator it;  // Error: need 'typename T::iterator'
    typename T::iterator it2;  // OK
}

Mistake 4: Passing by value for large types

template <typename T>
T max(T a, T b);  // Copies large objects

Use forwarding references or const T& when objects are expensive to copy.

Mistake 5: Overlooking ODR violations with inline

// In two translation units
template <typename T>
void func(T) {}
// OK: templates have implicit inline linkage

But if two files define the same non-template function, that violates ODR.

Practice Questions

  1. What is the output?
template <typename T>
T square(T x) { return x * x; }

int main() {
    std::cout << square(5) << " " << square(2.5);
}

Answer: 25 6.25 — compiler deduces int and double.

  1. Why does max(3, 7.5) fail to compile? Answer: Template argument deduction finds conflicting types (int vs double). Use max<double>(3, 7.5) or two type parameters.

  2. What is the difference between template <typename T> and template <class T>? Answer: They are identical. typename is preferred since C++98 to avoid confusion with class types.

  3. Can a non-type template parameter be a double or std::string? Answer: Only integral types, enumerations, pointers, references, and std::nullptr_t. Floating-point and class types are not allowed.

  4. Write a function template that returns the smaller of two values.

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

FAQ

What is a function template in C++

A function template is a generic function definition parameterized by one or more types or values. The compiler generates type-specific instantiations from it.

How does template argument deduction work

The compiler looks at the function arguments and deduces the template parameters that make the call valid. If deduction fails or produces conflicting types, compilation fails.

Can I put template definitions in .cpp files

Yes, but only if you explicitly instantiate every needed type. Otherwise, the definition must be visible in every translation unit that uses it, so headers are the standard location.

What are non-type template parameters

They are compile-time constant values passed as template arguments, like template <int N>. They enable compile-time computation and array sizing.

How does overload resolution choose between templates and regular functions

Non-template functions are preferred over templates when both are equally viable. Among templates, more specialized ones (more constraints) are preferred.

Mini Project

Write a generic print_container function template that works with any iterable container (vector, list, set, array, map) and prints its elements separated by commas:

#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>

// Your print_container template here

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    print_container(v);  // 1, 2, 3, 4, 5

    std::set<std::string> s = {"apple", "banana", "cherry"};
    print_container(s);  // apple, banana, cherry

    std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
    print_container(m);  // 1: one, 2: two
}

This exercise reinforces template parameter deduction, Iterator usage, and generic programming — the foundation of the C++ STL.

What's Next

Now you understand function templates, the foundation of generic programming. Next, you will extend these concepts to entire classes in C++ class templates. For a broader perspective on static typing, compare with Java generics which use type erasure instead of template instantiation.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro