C++ • C++-Specific Topics

Function Templates — Use function templates (intro)

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

Programming braces with a command terminal and binary digits.
Inside this lesson 10 sections 5 questions 6 figures

SECTION 1

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.

SECTION 2

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.

SECTION 3

Visual Explanation — From Template to Instantiation

Function Template Instantiation Flowtemplate<typename T>T max(T a, T b) { return (a > b) ? a : b; }Template Definition (no code generated yet)max(3, 7)max(1.5, 2.7)max('a', 'z')Calls trigger instantiationT = intint max(int a, int b) { return (a>b)?a:b; }→ returns 7T = doubledouble max(double a, double b) {...}→ returns 2.7T = charchar max(char a, char b) {...}→ returns 'z'Compiled BinaryContains 3 distinct functions: max<int>, max<double>, max<char>Each as efficient as a hand-written version
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.

SECTION 4

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<typenameT1,typenameT2,...,typenameTn>ReturnTypefunctionName(T1param1,T2param2,...)/∗body∗/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=doublemax(3,7)//implicit:Tdeducedasintmax<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.

SECTION 5

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 Template VariationsSingle Type Parametertemplate<typename T>T square(T x) { return x * x;}• One placeholder T• Return type = T• T requires operator*• Most common patternMultiple Type Parameterstemplate<typename T, typename U>auto add(T a, U b) { return a + b;}• Two placeholders T, U• auto deduces return type• Allows mixed-type callsNon-Type Parametertemplate<typename T, int N>T arraySum(T (&arr)[N]){ T s{}; for(int i=0;i<N;++i) s += arr[i]; return s; }• N is a compile-time intType Deduction Rules (Simplified)1. Compiler examines each argument's type2. Matches against the parameter pattern3. Deduces T (and U, etc.) independently4. All deductions for the same T must agreemax(3, 7.2) // ERROR: T=int vs T=doublemax<double>(3, 7.2) // OK: explicit T=doubleadd(3, 7.2) // OK: T=int, U=double (2 params)Overload Resolution with TemplatesWhen both a template and a non-templatematch, the non-template wins (more specific).int max(int a, int b); // non-templatetemplate<typename T>T max(T a, T b); // templatemax(3, 7); // calls non-templatemax(3.0,7.0);// calls template T=double
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).

SECTION 6

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. 1

    Step 1 — Identify the Problem

    We 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. 2

    Step 2 — Write the Template Definition

    We 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. 3

    Step 3 — Instantiate via a Call

    In 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. 4

    Step 4 — Reuse with a Different Type

    Now 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. 5

    Step 5 — Verify Type Safety

    Attempting 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.

SECTION 7

Strengths & Limitations of Function Templates

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.

Strengths and limitations of function templates in C++

✦

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.

SECTION 8

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.

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.

How introductory template concepts extend into advanced C++ features

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.

SECTION 9

Practice Problems

Question 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?
Show answer
A function template is a compile-time blueprint — a set of instructions for generating functions, not a function itself. No machine code is emitted because the compiler does not know which types to substitute for T. Only when a call such as identity(42) is encountered does the compiler perform instantiation, substituting T = int and compiling a concrete int identity<int>(int) function. Each unique type used in a call creates a separate instantiation in the binary.

Question 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?
Show answer
The output is 12 7.5. The call triple(4) deduces T = int and computes 4 + 4 + 4 = 12. The call triple(2.5) deduces T = double and computes 2.5 + 2.5 + 2.5 = 7.5. Two distinct instantiations are generated: triple<int> and triple<double>.

Question 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.
Show answer
The call fails because both parameters use the same template parameter T, but argument deduction yields T = int from the first argument and T = double from the second — a deduction conflict. Fix 1: explicitly specify the type with bigger<double>(3, 4.5), allowing the implicit conversion of 3 to 3.0. Fix 2: cast the first argument — bigger(static_cast<double>(3), 4.5) — so both arguments are double and deduction succeeds.

Question 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.
Show answer
The template uses both a type parameter and a non-type parameter: template<typename T, int N> void printArray(T (&arr)[N]) { for (int i = 0; i < N; ++i) { if (i > 0) std::cout << ", "; std::cout << arr[i]; } std::cout << std::endl; } A sample call: int data[] = {10, 20, 30, 40}; printArray(data); The compiler deduces T = int and N = 4 from the array's type, producing output: 10, 20, 30, 40.

Question 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?
Show answer
The instantiation with T = std::string is well-defined and compiles because std::string supports default construction (T sum{} yields an empty string) and operator+= (string concatenation). The function would concatenate all strings in the array. However, this may be semantically undesirable — a caller expecting numeric summation would be surprised. This illustrates that function templates impose only syntactic requirements (the type must support the operators used), not semantic ones (the operations must mean 'addition'). C++20 concepts allow you to constrain T to types satisfying a named concept such as std::integral or a custom Numeric concept, yielding a clear error message when a non-numeric type is passed and documenting intent directly in the interface.

SUMMARY

Summary — Function Templates at a Glance

Summary

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)