C++ • C++-SPECIFIC TOPICS

Function Templates — Use function templates (intro)

Write one function that works with any data type through compile-time polymorphism.

Historical Context & Motivation

Before function templates existed in C++, programmers faced a frustrating dilemma: every time they needed the same algorithm to operate on a different data type, they had to write an entirely separate function or resort to void* pointers that sacrificed type safety. If you wrote a max function for int, you needed another for double, and yet another for float — identical logic duplicated across overloads. This kind of code duplication violated the DRY (Don't Repeat Yourself) principle and was a persistent source of maintenance bugs. The C++ community needed a mechanism that could abstract over types while preserving the language's hallmark of compile-time type checking and zero-overhead abstraction.

1979
C with Classes
Bjarne Stroustrup begins extending C at Bell Labs. Early versions lack any generic programming facility — every function must be written for a specific type.
1986
Templates Proposed
Stroustrup drafts a design for parameterized types, inspired by Ada's generics and ML's parametric polymorphism, aiming for type-safe code reuse with no run-time overhead.
1990
Templates Implemented
The Cfront 3.0 compiler ships the first working implementation of templates. The feature is immediately adopted for container libraries and numeric algorithms.
1998
ISO C++98 Standard
The first ISO C++ standard formalizes templates, and the Standard Template Library (STL) — built entirely on templates — becomes part of the language's standard library.
2011–2020
Modern Template Evolution
C++11 adds variadic templates, C++17 adds if constexpr and class template argument deduction, and C++20 introduces concepts for constraining template parameters.

The fundamental question templates answer is deceptively simple: How can we write a single piece of logic that the compiler can stamp out for any compatible type, while retaining full static type checking and generating code as efficient as hand-written, type-specific versions? Function templates are the first — and arguably the most intuitive — answer C++ provides to this question.

Core Principles & Definitions

A function template is not a function itself — it is a blueprint from which the compiler generates actual functions. When you write template<typename T> T add(T a, T b) { return a + b; }, no machine code is produced until you actually call add(3, 5) or add(1.5, 2.7). At that point the compiler performs template instantiation, substituting the deduced type for T and compiling a concrete version of the function. This process is entirely a compile-time mechanism — no virtual dispatch, no run-time type information, and no overhead compared to manually writing each overload.

1

Template Parameter

A placeholder (e.g., T) declared in the template<typename T> prefix. It stands for any type supplied at instantiation.
2

Template Instantiation

The compiler generates a concrete function (e.g., add<int>) by substituting the template parameter with an actual type. Each unique substitution produces a distinct function.
3

Template Argument Deduction

When you call add(3, 5), the compiler examines the argument types and deduces that T = int — you do not need to write add<int>(3, 5) explicitly (though you can).
4

Implicit Requirements

The body of a template imposes implicit requirements on T. If the template uses operator+, then T must support addition, or compilation fails.
5

Zero-Overhead Abstraction

Because instantiation happens at compile time, the generated code is identical to a hand-written, type-specific function — no run-time indirection, boxing, or dynamic dispatch.
✦ KEY TAKEAWAY
Think of a function template as a cookie cutter. The cutter itself is not a cookie — it is the pattern. Each time you press it into a different dough (data type), you get a perfectly shaped cookie (function instantiation) specific to that dough. The cutter (template) is reusable, but every cookie (instantiation) is a distinct, fully baked entity. If the dough cannot hold the shape (the type does not support the required operations), the cookie crumbles — and the compiler reports a type error.

Visual Explanation — From Template to Instantiation

The template definition (top) is a blueprint that produces no code on its own. Each call site (middle row) triggers template instantiation, where the compiler substitutes T with the deduced type and emits a fully concrete function. All three instantiations appear independently in the compiled binary (bottom).

The diagram above illustrates the critical distinction between a template and its instantiations. The template<typename T> declaration at the top is purely a compile-time construct — it tells the compiler, "when someone calls this function with a concrete type, generate the appropriate version." Notice that three completely separate functions exist in the final binary; there is no shared run-time dispatch mechanism. This is static (compile-time) polymorphism in action, in contrast to the dynamic polymorphism achieved through virtual functions and inheritance. The compiler resolves which instantiation to call at compile time, enabling full inlining and optimization.

How Function Templates Work Under the Hood

Syntax Anatomy

A function template declaration always begins with the template keyword followed by a template parameter list enclosed in angle brackets. The keywords typename and class are interchangeable in this context (by convention, modern C++ favors typename). The template parameter T then stands as a placeholder type throughout the function signature and body.

GENERIC SYNTAX
template<typename T₁, typename T₂, ..., typename Tₙ> ReturnType functionName(T₁ param₁, T₂ param₂, ...) { /* body */ }
T₁ through Tₙ are template type parameters. Each can be independently deduced from function arguments or explicitly specified. ReturnType may itself depend on the template parameters.

Template Argument Deduction

When you invoke a function template without explicitly specifying the template argument, the compiler inspects the types of the actual arguments to deduce T. For example, calling max(3, 7) lets the compiler deduce T = int because both arguments are int. However, calling max(3, 7.2) produces a deduction conflict: the first argument suggests T = int while the second suggests T = double. The compiler will not guess which you intended — you must resolve the ambiguity by explicitly specifying max<double>(3, 7.2) or by casting one argument.

EXPLICIT VS IMPLICIT INSTANTIATION
max<double>(3, 7.2) // explicit: T = double max(3, 7) // implicit: T deduced as int
Explicit specification (angle-bracket syntax) overrides deduction and allows implicit conversions of arguments to the specified type.

The Instantiation Process

Once T is determined, the compiler performs two-phase name lookup. In the first phase (at template definition), names that do not depend on T are resolved immediately. In the second phase (at instantiation), names that depend on T are resolved using the concrete type. This means that a template with a syntax error in non-dependent code will fail at definition time, while errors involving T surface only when the template is actually instantiated with a specific type. This delayed error reporting is one reason template error messages have historically been notoriously verbose — a situation significantly improved by C++20 concepts.

⚠️ Definition Must Be Visible
Unlike regular functions, the full definition of a function template (not just the declaration) must be visible at every call site. In practice, this means template definitions are placed in header files, not in .cpp files. The compiler needs the body to stamp out each instantiation during compilation of each translation unit.

Template Variations & Type Deduction Details

Function templates come in several forms depending on how many type parameters they accept, whether non-type parameters are involved, and how the return type is determined. Understanding these variations is essential before moving on to advanced template metaprogramming in later courses.

Function templates can accept one or more type parameters, non-type (value) parameters such as array sizes, and can interact with non-template overloads through C++'s overload resolution rules. The bottom-left box summarizes deduction conflict scenarios, while the bottom-right explains how ordinary overloads take priority over template instantiations when both match.

An important nuance visible in the diagram is the use of auto as a return type in the multi-parameter template. In C++14 and later, auto allows the compiler to deduce the return type from the return statement, which is particularly useful when T and U are different types and the return type is the common type produced by the expression a + b (e.g., int + double yields double). In C++11, you would use a trailing return type: -> decltype(a + b).

Worked Example — A Generic Swap Function

Let us walk through writing, instantiating, and using a function template that swaps two values of any type. This is a canonical example because std::swap in the standard library is itself a function template.

Building a Generic mySwap Function Template
1
Step 1 — Identify the ProblemWe need a function that swaps two variables in place. Without templates, we would write separate versions for int, double, std::string, etc. — all with identical logic.
2
Step 2 — Write the Template DefinitionWe declare a template parameter T and use it throughout the function. The parameters must be passed by reference so changes propagate to the caller: template<typename T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; }
Template defined — no machine code generated yet.
3
Step 3 — Instantiate via a CallIn main(), we call mySwap with two int variables. The compiler deduces T = int and generates void mySwap<int>(int&, int&): int x = 10, y = 20; mySwap(x, y); // T deduced as int // x == 20, y == 10
x = 20, y = 10 after the swap.
4
Step 4 — Reuse with a Different TypeNow call the same template with std::string arguments. A second instantiation mySwap<std::string> is generated: std::string a = "hello", b = "world"; mySwap(a, b); // T = std::string // a == "world", b == "hello"
a = "world", b = "hello".
5
Step 5 — Verify Type SafetyAttempting mySwap(x, a) where x is int and a is std::string produces a compile-time error — the compiler deduces T = int from the first argument and T = std::string from the second, yielding a deduction conflict. Type safety is preserved at compile time with zero run-time cost.
Compilation error: conflicting deduction for T.

Strengths & Limitations of Function Templates

Strengths and limitations of function templates in C++
AspectStrengthLimitation
Type SafetyFull compile-time checking; no casts, no run-time type errors.Error messages can be long and cryptic, especially in deeply nested templates (mitigated by C++20 concepts).
PerformanceZero overhead — each instantiation is optimized independently, enabling full inlining.Code bloat: many unique instantiations increase binary size since each type gets its own copy of the function.
Code ReuseA single template replaces potentially dozens of overloads; maintenance effort is concentrated.Definitions must live in header files, exposing implementation details and increasing compile times.
FlexibilityWorks with any type that satisfies the template's implicit requirements (duck typing at compile time).Implicit requirements are undocumented in the code — readers must inspect the body to know what T needs. (C++20 concepts solve this.)
DebuggingErrors caught at compile time rather than run time.Stepping through template code in a debugger can be unintuitive because the source is generic but execution is type-specific.
✦ KEY TAKEAWAY
Function templates trade compile time and binary size for run-time speed and type safety. Think of it like a factory that builds custom machines (instantiations) for each client (type). The engineering blueprint (template) is designed once, and every machine built from it is purpose-built — fast and precise — but having many clients means a larger factory floor (binary). In most applications, the performance benefit vastly outweighs the size cost.

Connection to Advanced Template Techniques

Function templates are the gateway to a rich ecosystem of compile-time programming in C++. Once you internalize the basics presented in this lesson, you will encounter increasingly powerful techniques that build upon the same foundational idea of parameterizing code over types.

How introductory template concepts extend into advanced C++ features
This Lesson (Intro)Advanced TopicKey Extension
Single function templateTemplate SpecializationProvide a custom implementation of a template for a specific type (e.g., optimize max for C-strings that need strcmp).
Implicit requirements on TC++20 ConceptsExplicitly name and enforce requirements using concept and requires clauses, producing clear error messages.
Fixed number of type paramsVariadic TemplatesAccept an arbitrary number of template parameters using template<typename... Args>, enabling functions like std::make_unique.
Function templates onlyClass TemplatesParameterize entire classes (e.g., std::vector<T>, std::map<K,V>). Same instantiation model, applied at the class level.
Run-time computationconstexpr + TemplatesCombine constexpr with templates to evaluate generic algorithms entirely at compile time, embedding results directly into the binary.

As you progress, you will find that templates underpin nearly every modern C++ library — from the STL containers and algorithms to frameworks like Eigen (linear algebra) and Boost. The template metaprogramming paradigm takes the instantiation mechanism to its logical extreme, using the compiler itself as a Turing-complete computation engine that runs at compile time. For now, mastering basic function templates gives you the vocabulary and mental model to approach these advanced topics with confidence.

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain the difference between a function template and a function template instantiation. Why does writing template<typename T> T identity(T x) { return x; } not produce any machine code on its own?
PROBLEM 2 — BASIC CALCULATION
Given the following template: template<typename T> T triple(T x) { return x + x + x; } What is the output of std::cout << triple(4) << " " << triple(2.5);? How many instantiations are generated?
PROBLEM 3 — INTERMEDIATE
Consider the template: template<typename T> T bigger(T a, T b) { return (a > b) ? a : b; } Why does bigger(3, 4.5) fail to compile? Provide two different ways to fix it without changing the template definition.
PROBLEM 4 — APPLIED
Write a function template printArray that takes a reference to a C-style array of any type and any compile-time-known size N, and prints each element separated by commas. Use a non-type template parameter for the size. Show a call with an int array of size 4.
PROBLEM 5 — CRITICAL THINKING
Suppose you write a function template template<typename T> T accumulate(T* arr, int n) that sums n elements starting from a zero-initialized accumulator T sum{}. Analyze what happens when this template is instantiated with T = std::string. Is the result well-defined? What does this reveal about the implicit requirements function templates impose, and how might C++20 concepts address the issue?

Summary — Function Templates at a Glance

A function template is a compile-time blueprint that enables you to write a single piece of logic parameterized by one or more types. When you call the template with specific arguments, the compiler performs template argument deduction to determine the concrete type and then generates a fully typed function through template instantiation. This mechanism delivers zero-overhead abstraction — the generated code is identical to hand-written, type-specific functions — and preserves full compile-time type safety.

Key syntax elements include the template<typename T> prefix and the use of T as a placeholder type in the function signature and body. Templates may accept multiple type parameters and non-type parameters (like array sizes). When deduction conflicts arise from mismatched argument types, you can resolve them via explicit template arguments or casts. Template definitions must reside in header files so the compiler can see the full body at every call site. As you advance, function templates serve as the foundation for class templates, specialization, variadic templates, and C++20 concepts.

Varsity Tutors • C++ • Function Templates — Use function templates (intro)