Concepts / Parameterization of Containers and Algorithms

Parameterization of Containers and Algorithms

What changes from one container to the next is only the kind of element being added.

  • Programming

The Problem of Repeating the Same Function

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. Instead of writing the function once for each type, you write it once and leave the type open until the function is actually used. The type is a parameter, just like a value passed into an ordinary function, except that the thing being passed is a type rather than a number or a string.

What do you think happens?

If you write one summing function that works on a container of integers, will that same written code also work on a container of decimal numbers, with no changes to the text of the function?

  • Yes, if the function is written as a template

  • No, every element type always needs its own copy of the code

Reveal answer
Answer:

Yes, if the function is written as a template.

A template leaves the element type open. The pattern becomes a concrete function once a type is supplied at the point of use, so the same text can serve integers, decimal numbers, and other element types.

One Pattern, Many Element Types

The source gives a concrete illustration: a function that calculates the sum of the element values of any container. Read this as a plan rather than as one fixed function. The job is always the same, which is to 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. The single definition is the pattern. Each time the code uses the pattern with a particular type, a concrete function comes out of it. The diagram below shows one template feeding several concrete functions, with the shared job held in the template itself.

Walk and addfixed jobsum_values templatetype parameter T opensum_values with intT = intsum_values withdoubleT = doublesum_values withstd::stringT = std::stringcaptured bysupply Tsupply Tsupply T
One definition of sum_values with a type parameter T turns into separate concrete functions for int, double, and std::string, while the walk-and-add job stays the same in each.

Tracing a Template Call to Concrete Code

Consider a small function template that returns the larger of two values. Its declaration begins with template and a type parameter named T, and both parameters use T. When the code calls max(3, 7), the type is taken from the arguments, so T becomes int. When the code calls max(2.5, 1.0), T becomes double. The same pattern produces two separate concrete functions, one for each use.

cpp
Notes on the code
  • template<typename T> announces that T is a type parameter left open in the pattern
  • Both parameters and the return type use T, so they must agree
  • max_of(3, 7) supplies int at the point of use
  • max_of(2.5, 1.0) supplies double at the point of use
Worked example

Following two calls through the compiler

Trace what happens when the code calls max_of(3, 7) and then max_of(2.5, 1.0).

  1. 1
    Read the first call

    The arguments 3 and 7 are both int values, so the type parameter T is deduced as int.

  2. 2
    Generate the first version

    The pattern is filled in with T = int, producing a concrete function that works on int values.

  3. 3
    Compile the first version

    The concrete int function is compiled like any ordinary function and used for the call.

  4. 4
    Read the second call

    The arguments 2.5 and 1.0 are both double values, so T is deduced as double this time.

  5. 5
    Generate the second version

    The same pattern is filled in with T = double, producing a second, separate concrete function.

  6. 6
    Compile the second version

    The double function is compiled separately, and the call uses it.

Result

One template text produced two concrete functions, one for int and one for double. The first call returns 7 and the second returns 2.5.

max_of(3, 7) callint argumentsDeduce T as intfrom both argumentsGenerate max_of forintCompile int versionmax_of(2.5, 1.0) calldouble argumentsDeduce T as doublefrom both argumentsGenerate max_of fordoubleCompile doubleversionread argumentsT = intconcrete functionnext callread argumentsT = doubleconcrete function
Each call deduces T from its arguments, generates a version for that type, and compiles it. The two calls produce two separate functions from the same pattern.

Templates Across the Standard Library

The source points out that templates have more uses than parameterizing a container with an element type. Two uses are singled out. Templates are used extensively to parameterize types, so that a container can hold elements of whatever type the programmer names. Templates are also used to parameterize algorithms, which are the procedures applied to the data. Both appear throughout the standard library.

The same template idea therefore covers two kinds of generality. One concerns what kind of element a container holds. The other concerns the procedure applied to the data. A container such as std::vector is generic over its element type, and an algorithm written as a template can be applied to data of many element types without being rewritten for each one.

Writing the Summing Function as a Template

Returning to the summing example from the source, the following template walks through a std::vector of any element type and adds the values together. The total starts at a value-initialized T, so the same text works whether T is int or double. Each use with a different element type produces its own concrete function.

cpp
Output
6
4
Notes on the code
  • The parameter T is the element type, which appears inside std::vector<T>
  • The job of walking and adding stays fixed in the loop
  • T total{} starts at zero for numeric types
  • The first call produces an int version and the second a double version

Template Versus Separate Overloads

The alternative to a template is to write a separate function for each element type. The two approaches differ in how much code is repeated. With separate functions, the logic is copied for every type, and a later change to that logic has to be made in every copy. With one template, the logic lives in a single definition, and each type is supplied only where it is used.

One definitionT supplied per useOne body to maintainint copydouble copystring copy
A single template keeps one body of logic that is reused for every type, while separate functions repeat the same body once per type.

Mistakes When Reading Template Parameters

  • Common mistake

    Treating the template as one function that runs for every type

    What it looks like

    Believing max_of exists once in memory and simply accepts any type at run time

    Why it fails

    The source describes a template as a pattern that becomes a concrete function once a type is supplied at the point of use. Each type used produces its own concrete version.

    Do this instead

    Think of the template as a recipe. Each type used produces a separate function from that recipe.

  • Common mistake

    Assuming T can be any type regardless of what the body does

    What it looks like

    Calling sum_values on a container whose element type has no += operation

    Why it fails

    The template body uses += on each element, so the element type must support that operation. Leaving T open does not remove the need for the operations the body performs.

    Do this instead

    Check that the element type supports every operation the template body uses.

  • Common mistake

    Mixing argument types in one call to a template with a single T

    What it looks like

    Writing max_of(3, 2.5) and expecting it to choose a type automatically

    Why it fails

    Both parameters are declared with the same T, so the two arguments must agree on one type for the deduction to succeed.

    Do this instead

    Make the arguments the same type, for example by writing max_of(3.0, 2.5) or by converting one argument first.

Practice: Predicting the Concrete Versions

EASY

Consider the template max_of shown earlier. For each of the following calls, state the type T that would be deduced, and say how many separate concrete functions the code produces in total: max_of(10, 4), max_of(1.5, 9.0), and max_of(2, 8). Explain why the third call does not produce a new function.

Hints
  • Look at the type of each argument, not the value.
  • Two calls that deduce the same T share one concrete version.
  • The number of concrete functions equals the number of distinct types deduced.

Key Takeaways

  1. 1
    A function template parameterizes a function with a type, 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
    Templates parameterize both the types a container holds and the algorithms applied to data, and both are used throughout the standard library.
  4. 4
    Each distinct type used with a template produces its own concrete function from the same pattern.

At a glance

  • A function template parameterizes a function with a type, so one definition can serve many element types.
  • The template is a pattern that becomes a concrete function once a type is supplied at the point of use.
  • Templates are used to parameterize both the types held by containers and the algorithms applied to data in the standard library.
  • Tracing a call shows the compiler deducing T from the arguments, generating a version for that type, and compiling it.

Related concepts

More concepts starting with P