Concepts / Introduction to Function Templates

Introduction to Function Templates

A function template parameterizes a function with types, so one definition can serve many element types.

  • Programming

The Repetition Problem Behind Templates

Imagine you need a function that adds up the values stored in a collection. Today the collection holds integers. Tomorrow it holds decimal numbers, and the day after it holds some other kind of element. If every one of these cases needed its own separately written function, most of the logic would be copied again and again, changing only the type named in the code.

A function template is a way to avoid that repetition. You write the function once and leave the type open until the function is actually used. The rest of this article explains what it means to leave a type open, how the compiler fills it in, and why this idea matters so much in the standard library.

Predicting the Output of a Generic Sum

Before reading the explanation, look at the program below. It contains one function that sums the values of any container of numbers. Try to decide what it prints, and notice how the same function body handles two different element types.

cpp

What do you think happens?

What does the program print on each of its two lines?

  • 12 and 3.75

  • 12 and 3

  • 3 and 3.75

  • It does not compile because the two vectors hold different types

Reveal answer
Answer:

12 and 3.75

The first call adds 3, 4, and 5 to get 12. The second call adds 1.5 and 2.25 to get 3.75. The function body is identical in both calls; only the element type differs.

Parameterizing a Function With a Type

Definition

A function template parameterizes a function with types, so one definition can serve many element types. The template is a pattern. It becomes a concrete function once a type is supplied at the point of use.

The source's central example is a function that calculates the sum of the element values of any container. Read that function as a plan rather than as a single function. The job is always the same: walk through the elements and add their values together. What changes from one container to the next is only the kind of element being added. A function template captures the fixed job and leaves the element type open.

In the program above, the line template <typename T> introduces T as a type parameter. T is a placeholder name. Everywhere the body says T, the compiler will eventually substitute a real type such as int or double. The words typename T mean that T stands for some type, not for a value.

sum_values templateT left opensum_values<int>generated for intsum_values<double>generated for doublesum_values<std::string>generated only if calledwith stringsT = intT = doubleT = std::string
A single sum_values template with an open type T becomes a separate concrete function for each type that is actually supplied. The template text itself is never called directly.

Tracing Two Calls Through One Template

Worked example

Following sum_values(counts) from call to result

The call sum_values(counts) is made, where counts is a std::vector<int> holding 3, 4, and 5. Trace how the compiler decides which version of the template to use and what the function computes.

  1. 1
    Read the argument type

    The argument counts has type std::vector<int>. The parameter of the template is const std::vector<T>&, so the compiler matches the pattern and sees that T must be int.

  2. 2
    Fill in the type parameter

    With T equal to int, the compiler produces a concrete function sum_values<int>. Every occurrence of T in the body now means int.

  3. 3
    Initialize the total

    The line T total = T(); becomes int total = int(); which starts total at 0.

  4. 4
    Accumulate the elements

    The loop visits 3, then 4, then 5, adding each one to total. After the loop, total holds 12.

  5. 5
    Return the result

    The function returns 12 as an int, and the program prints 12.

Result

The call prints 12. The second call, sum_values(prices), follows the same steps with T equal to double and prints 3.75.

countsstd::vector<int>match against conststd::vector<T>&pattern lines up with TT = intsum_values<int>concrete function calledtype passed indeducedsubstituted
The type of the argument at the call site flows into the template parameter T. The compiler uses that type to choose which concrete version of the template to create and call.
Before
template <typename T>
T sum_values(const std::vector<T>& items) {
    T total = T();
    for (const T& value : items) {
        total += value;
    }
    return total;
}

The template text contains the placeholder T. No function with that exact text is called at run time.

After
// Generated for the call sum_values(counts)
int sum_values(const std::vector<int>& items) {
    int total = int();
    for (const int& value : items) {
        total += value;
    }
    return total;
}

T has been replaced by int everywhere. The result is an ordinary function that the compiler can call like any other.

Why Templates Matter in the Standard Library

The source points out that templates have many more uses than simply parameterizing a container with an element type. Two uses are singled out: templates are used extensively to parameterize types, and they are also used to parameterize algorithms. Both appear throughout the standard library.

The same template idea therefore covers two kinds of generality. One is about what kind of element a container holds. The other is about the procedure applied to the data. A container template can hold any element type, and an algorithm template can operate on elements of many types without its source code being rewritten for each one.

std::find on intelements compared with ==std::find on doubleelements compared with ==std::find on Pointneeds == defined for Point
The same generic search template, such as std::find, runs over elements of different types. Its source code stays the same; what changes is the element type and whatever comparison that type supports.

The comparison above is a generated illustration. The point it makes is that a generic algorithm is written once against an open type, so the same procedure can be reused across element types. Each type has to support the operations the algorithm relies on.

Mistakes Beginners Make With Templates

  • Common mistake

    Treating a template as one ordinary function that works for every type automatically

    What it looks like

    Writing the template and then assuming the body is valid for a type such as a plain struct without checking the operations used inside it.

    Why it fails

    The template body is checked against the type it is instantiated with. If that type does not support an operation such as +=, the code using it will not compile.

    Do this instead

    Think about which operations the template body requires, and make sure each type used with the template supports them.

  • Common mistake

    Expecting a template to exist as machine code before any call is made

    What it looks like

    Looking for a compiled sum_values function in the program when the program never calls it.

    Why it fails

    Concrete functions are produced for the types actually used at call sites. A template with no uses produces no concrete function.

    Do this instead

    Remember that the template is a pattern, and the concrete function is generated from it once a type is supplied.

  • Common mistake

    Confusing a type parameter with a value parameter

    What it looks like

    Writing template <typename T> and then trying to pass T as a number inside the body.

    Why it fails

    T stands for a type such as int or double. It is not a variable holding a value.

    Do this instead

    Use T where a type is needed, such as in a declaration, and use ordinary parameters for values.

  • Common mistake

    Forgetting that the same body is reused for every type, so a fix made for one type applies to all of them

Writing a Template for Your Own Choice of Type

EASY

Write a function template named largest_of that takes two values of the same type T and returns the larger one, using the greater than operator. Then call it once with two int values and once with two double values. Predict the output of each call before you run the code.

Hints
  • Start with template <typename T> followed by a function whose parameters and return type use T.
  • Both parameters must have the same type T, so the two call arguments must match in type.
  • Compare the two values with > and return whichever is larger.

Key Points to Carry Forward

  1. 1
    A function template parameterizes a function with types, so one definition can serve many element types.
  2. 2
    The template is a pattern that becomes a concrete function once a type is supplied at the point of use.
  3. 3
    The same fixed job, such as summing values, can stay in one function while the element type changes.
  4. 4
    Templates are used extensively in the standard library to parameterize both types and algorithms.

Key Takeaways

  • A function template parameterizes a function with types, so one definition can serve many element types.
  • A template is a pattern that becomes a concrete function once a type is supplied at the point of use.
  • Templates are used extensively to parameterize both containers' element types and algorithms in the standard library.
  • Concrete functions are generated for the types actually used at call sites, and each type must support the operations the template body uses.

Related concepts

More concepts starting with I