Concepts / Generic Programming with Templates

Generic Programming with Templates

Passing a type to a template gives the template the exact type, not an approximation. This preserved detail is the reason the compiler can produce code tailored to that type.

  • Programming

Where Ordinary Functions Run Out of Room

Reusable code is a constant goal in programming. A function that adds two numbers is useful, but a programmer often wants the same logic to work for many kinds of data without rewriting it each time. Templates are a feature designed to address this need. They let a single piece of code serve many situations, and they do so by moving certain decisions out of the running program and into the process that turns source code into an executable.

What do you think happens?

When the compiler meets a template, does it check the template body for type errors right away, or only once the template is used with concrete arguments?

  • Right away, when the template is written

  • Only when the template is used with concrete arguments

Reveal answer
Answer:

Only when the template is used with concrete arguments

The source states that type checking in a template is delayed until instantiation, which is the point where the template is used with concrete arguments and a version of it is actually produced. Until then, the template is a pattern that has not yet been filled in.

Three Capabilities Under One Idea

A useful way to understand templates is to ask what they make possible that ordinary functions and ordinary values do not. The answer is that templates give a program three distinct capabilities. Each one addresses a different limit a programmer would otherwise run into. The first is that templates can accept types, values, and other templates as arguments. The second is that type checking in a template is delayed until the template is instantiated. The third is that constant values passed as arguments allow computation to happen at compile time.

All three capabilities share a common theme: each one moves decisions out of the running program and into the compilation process. The diagram below shows how the three ideas relate to one another and to the broader goal of generic programming.

Generic Programmingwith TemplatesPassing types,values, templatesexact information keptCompact, efficientcodeDelayed type checkingchecked at instantiationCompile-timeconstantswork done earlyprovidesprovidesprovidesexact types enable inliningeach use brings its own contextconstant work done early
How do the three capabilities relate to one another and to generic programming as a whole?

The source describes the three ideas as reinforcing one another. Types carry exact information, checking waits for concrete use, and constants can be evaluated early. Combined, these produce a mechanism that can be very compact and efficient, which is why the source calls it a powerful one.

Passing a Type Without Losing Its Identity

The first capability is that a template can accept a type as an argument, in the same way a function accepts a value. The important phrase is without loss of information. When a type is handed to a template, the template receives the type itself, not a stand-in or a simplified description of it. Because of this, the code that is produced can be built around that exact type.

The same passing mechanism works for values and for templates. The source emphasizes that the ability extends to types as well as values and templates. Keeping all of this information intact is what gives the compiler the chance to optimize, and that opportunity is the subject of the next idea.

Key point

Passing a type to a template gives the template the exact type, not an approximation. This preserved detail is the reason the compiler can produce code tailored to that type.

Why Exact Types Create Room for Inlining

Because the exact type survives the trip into the template, the compiler can make its generated code specific to that type. Inlining is the practice of placing the body of a called routine at the point where it is called. The source connects the preserved type information directly to this practice, noting that it creates excellent opportunities for inlining and that current implementations take great advantage of them.

The reasoning can be followed step by step. A template receives a type argument. The type is kept whole. The compiler therefore has a version of the code that is built for that specific type. A routine that exists in a version written for exactly that type is a natural candidate for having its body placed directly at the call site. The source does not go into further detail about the particular optimizations involved, so the key idea to hold on to is simply that exact type information is what makes the specialized code possible.

Delayed Type Checking and Instantiation

The second capability concerns when a template's types are checked. The source states that type checking in templates is delayed until instantiation. Instantiation is the point at which a template is used with concrete arguments and a version of it is actually produced. Until that point, the template is a pattern that has not yet been filled in, so the checking that depends on specific types waits for those specific types to arrive.

The source describes the benefit as an opportunity to weave together information from different contexts. The template itself does not need to know every situation in which it will be used. Each use supplies its own context, and the checking that happens at that moment draws on the information that context provides. A single template can therefore serve many callers, each bringing different information with them.

Template writtenpattern with openparametersWaits as a patternno type checks yetInstantiatedconcrete arguments suppliedType checks runagainst this use's typesCode produced orerror reportedused by a caller
When is a template's body actually checked for type errors, and what steps happen between writing the template and the compiler reporting a problem?

Compile-Time Constants and factorial<5>

The third capability is the ability to pass constant values as arguments. The source draws the conclusion that this implies the ability to do computation at compile time. The reasoning is direct. If the value handed to a template is a constant, the compiler already knows it when the template is instantiated. Any work that depends only on that constant can therefore be carried out before the program runs.

A small illustrative example makes this concrete. Suppose a template named factorial takes a constant n and is defined so that factorial of n equals n multiplied by factorial of n minus 1, with factorial of 0 equal to 1. The example below is generated for teaching purposes and is not taken from the source. It shows how a constant argument drives a chain of steps that the compiler can finish before execution.

factorial<5>constant argument 55 × factorial<4>waiting on inner result4 × factorial<3>waiting on inner result3 × factorial<2>waiting on inner result2 × factorial<1>waiting on inner result1 × factorial<0>factorial<0> = 1base case reachedFinal value 120finished at compile timevalues multiplied back up
How does a template instantiated with a constant argument produce a finished value during compilation, and what does each recursive step look like?
Worked example

Finishing factorial<5> Before the Program Runs

Using the illustrative definition in which factorial of n equals n multiplied by factorial of n minus 1, with factorial of 0 equal to 1, determine the value of factorial<5> and explain when it is produced.

  1. 1
    Start with the constant

    The argument 5 is a constant, so the compiler knows it at instantiation. Nothing about it depends on the running program.

  2. 2
    Unfold the first step

    Using the definition, $5! = 5 \times 4!$. The inner value 4! is still unknown, but it depends only on the constant 4, which is also known.

  3. 3
    Keep unfolding

    Repeating the definition gives $4! = 4 \times 3!$, then $3! = 3 \times 2!$, then $2! = 2 \times 1!$, then $1! = 1 \times 0!$.

  4. 4
    Reach the base case

    The definition gives $0! = 1$, which is a fixed value that needs no further unfolding.

  5. 5
    Multiply back up

    Substituting upward gives $1! = 1$, $2! = 2$, $3! = 6$, $4! = 24$, and finally $5! = 120$.

Result

factorial<5> = 120, a finished value produced during compilation. Because the work depended only on the constant argument, none of it has to happen while the program runs.

Mistakes Learners Make with Template Timing

  • Common mistake

    Assuming a template body is fully checked as soon as it is written

    What it looks like

    A template is written and no error is reported, so the learner concludes every possible type has been verified.

    Why it fails

    The source says type checking is delayed until instantiation, so the checks that depend on specific types have not yet run when the template is only written.

    Do this instead

    Treat the template as a pattern. Its type checks happen for each concrete use, so a problem may appear only when a particular set of arguments is supplied.

  • Common mistake

    Thinking a type argument is reduced to a simpler description

    What it looks like

    A learner imagines the template receives a rough label for a type rather than the type itself.

    Why it fails

    The source stresses that the template receives the type itself, without loss of information, which is why the generated code can be built around that exact type.

    Do this instead

    Think of a type argument as the complete type, and remember that this completeness is what enables specialized code and inlining.

  • Common mistake

    Believing that constant arguments are always evaluated while the program runs

    Why it fails

    When a template receives a constant, the compiler already knows that value at instantiation, so work depending only on it can be done before the program runs.

    Do this instead

    Ask whether the inputs are known at compile time. If they are constants, the related computation can be carried out during compilation.

  • Common mistake

    Confusing the three capabilities as separate features rather than one mechanism that reinforces itself

Tracing a Template Through Your Own Uses

MEDIUM

Imagine one template used in two places: once with a type that holds whole numbers, and once with a type that holds decimal numbers. Trace what happens for each use. Which steps are shared, which are specific to each use, and at which point would you expect a type problem to be reported if one exists?

Hints
  • Start with the template as a pattern, then ask what extra information each call site adds.
  • Recall that checking waits for instantiation, so each use triggers its own checks.
  • Think about whether the exact type is kept and whether it lets the compiler build code for that type.
EASY

Explain in your own words why a constant argument such as 5 allows work to be finished before the program runs, and why the same reasoning would not apply to a value that is only known when the program is running.

Hints
  • Consider when the compiler first learns the value.
  • Consider what the compiler can finish early if its input is already fixed.

Key Takeaways

  • Templates provide three capabilities: passing types, values, and templates as arguments; delaying type checking until instantiation; and passing constant values for compile-time computation.
  • Passing a type as an argument keeps its exact information, which lets the compiler build code specific to that type and creates excellent opportunities for inlining.
  • Type checking in a template happens at instantiation, so the same template can draw on information from different contexts for each use.
  • Constant values passed as arguments let work that depends only on them be completed at compile time, as in the illustrative factorial example.

Related concepts

More concepts starting with G