Concepts / Function Templates

Function Templates

Design and debug a non-template version first, then generalize it by adding parameters.

  • Programming

Why Templates Start Concrete

A template is a way to write one piece of code that works for many argument types. That is a powerful idea, and it creates a tempting shortcut: since a template is more general than any single version of the code, why not write the general version right away? The guidance on templates pushes against that impulse. A template is easier to get right when it grows out of a version that already works for one concrete case.

This article follows that order. You will see how to start from a working non-template function, how to generalize it by adding a parameter, why type checking in templates happens later than you might expect, how a function template can help determine the argument types that a class template needs, and how the whole mechanism fits into compile-time programming.

Designing the Concrete Version First

The design process has a clear order. First, you write a version that is not a template. It targets one concrete argument type, and you debug it until it behaves correctly. Second, you generalize that working code by adding parameters. The parameter stands in for the concrete type, so the same logic can be reused for other types.

  1. 1
    Write an ordinary, non-template function that handles one concrete argument type.
  2. 2
    Debug that function until it behaves correctly for the cases you care about.
  3. 3
    Generalize the working code by adding a template parameter in place of the concrete type.
  4. 4
    Re-check the generalized version, knowing that any new bugs most likely come from the generalization step itself.

The reason for this order is about where bugs can hide. Starting from a debugged concrete version means that any problems left after generalization are most likely caused by the generalization step, not by the underlying logic. The concrete version is a reference point: when the template misbehaves, you can compare it against code you already know works.

Turning a Concrete Function into a Template

Consider a small function that adds two integers. The concrete version below works for one argument type only. The template version, shown second, is a generated illustration of the same logic with a type parameter T in place of the concrete type. The body of the function does not change; only the declaration around it does.

Before
int add_values(int a, int b) {
    return a + b;
}

One concrete type, int, is written directly into the parameters and return type.

After
template <typename T>
T add_values(T a, T b) {
    return a + b;
}

A parameter T stands in for the concrete type, so the same logic can be used for other argument types.

Worked example

Generalizing the add function

Starting from the working int version, produce a template that reuses the same logic for other types.

  1. 1
    Confirm the concrete version works

    Test add_values with int arguments and check the results before changing anything.

  2. 2
    Choose the parameter

    The concrete type int appears in the parameters and the return type. Both are replaced by the single parameter T.

  3. 3
    Add the template header

    The line template <typename T> introduces T so the compiler knows the function is a template with one type parameter.

  4. 4
    Leave the logic alone

    The body return a + b is unchanged. The generalization is about the types, not the algorithm.

Result

A function template that follows the same logic as the debugged concrete version and can be used with other argument types.

Notice what stays the same. The name, the body, and the overall shape of the function remain. What changes is that the type is no longer fixed. That is why the concrete version is so useful as a starting point: almost everything you debugged in it carries over directly into the template.

Type Safety and Late Type Checking

Templates are type-safe. Their argument types are checked, so a template is not a loose text substitution that accepts anything. The source adds an important qualification: the checking happens late in the compilation process. A type problem may not be reported at the point where you wrote the template. It can surface later, when the compiler works through the code and the template is used with actual argument types.

Template writtenType parameter T declaredTemplate usedActual argument typessuppliedCompiler worksthrough codeCompilation proceedsTypes checkedLate in compilationCompiled functionConcrete code for thosetypeslater
What happens step by step from writing the template to the compiled function, and at which stage the types are checked.

The timing matters for debugging. Because a problem can appear after you wrote the template, you should not treat the template's correctness as something you can verify by reading it alone. This is one reason the guidance keeps the concrete, non-template version around while you are debugging: it gives you a fixed point to compare against when the late check reports something unexpected.

A related property is that a template can pass argument types along without losing information. When a value of a particular type is handed to a template, the template can carry that type through its code rather than forcing it into a more general or less precise type. This is what lets a generalized function remain correct for each type it is used with. The source describes this as a property of the template mechanism and does not spell out the internal steps by which it happens, so this article does not go further into that detail.

Deducing Types for Class Templates

A function template can be used to deduce the argument types of a class template. Instead of spelling out every type argument for a class template, you pass values through a function template, and the function template works out the types from those values. The deduced type then supplies the class template's argument. This saves you from writing types out by hand at each place the class template is used.

Function argumentA value passed at the callsiteFunction templateWorks out the typeDeduced typeTaken from the valueClass templateargumentSupplied by the deducedtypefeedsdeducessupplies
How the argument types at a call site are taken in and used to fill in the class template's type argument.

Read the diagram as a chain of hand-offs. The value passed at the call site is the only thing you write explicitly. The function template examines it, the type is deduced from it, and that type becomes the class template's argument. The benefit is that the type is never written twice, so the class template and the values it works on stay consistent.

One Template, Many Functions

The source describes templates as a general mechanism for compile-time programming. The work a template does is tied to compilation, which is also when its type checking takes place. A single template definition is therefore not one function that runs for every type. It is a description from which concrete code is produced for the types it is used with.

Template definitionOne piece of generic codeVersion for intConcrete functionVersion for doubleConcrete functionVersion for anothertypeConcrete functionCompile-time workType checking andgenerationfor intfor doublefor another type
How a single template definition relates to the concrete functions produced for different argument types.

The diagram shows the relationship in one direction. A single template definition sits at the top, and the functions for different argument types are the concrete results. The compile-time work is what connects the general description to the specific functions. The source does not detail those steps, so the broad picture is the important part: one generic definition, used with several types, supported by compilation.

Common Mistakes When Generalizing

  • Common mistake

    Writing the template immediately, before any concrete version works

    Why it fails

    A template is more general than any single version, so bugs are harder to locate. Without a debugged concrete version there is no reference point.

    Do this instead

    Write and debug the non-template function for one concrete type first, then generalize it.

  • Common mistake

    Assuming a template error will appear exactly where the template is written

    Why it fails

    Templates are type-checked late in compilation, so a type problem may only be reported when the compiler works through the code with actual types.

    Do this instead

    Expect that problems may surface later, and keep the concrete version to compare against while debugging.

  • Common mistake

    Changing the algorithm while generalizing

    Why it fails

    If the logic changes at the same time as the types, you cannot tell whether a new bug comes from the generalization or the logic.

    Do this instead

    Replace only the concrete type with the parameter and leave the body of the function as it was.

  • Common mistake

    Writing out every type argument for a class template by hand

    Why it fails

    This repeats information that is already present in the values being passed, and it risks the types falling out of step with those values.

    Do this instead

    Pass the values through a function template so the argument types are deduced for the class template.

Practice: Generalize a Concrete Function

MEDIUM

A debugged concrete function returns the larger of two int values. Describe, in order, the steps you would take to turn it into a template, and state which parts of the function should stay unchanged. Then explain why you would keep the original int version while you debug the template.

Hints
  • Start by identifying the concrete type that appears in the parameters and the return type.
  • Think about which part of the code is the logic and which part is the type information.
  • Consider where a late type problem might be reported, and what the concrete version gives you to compare against.

Key Takeaways

  • A template should begin as a non-template version for one concrete type, which is designed and debugged first.
  • Generalizing means adding a parameter in place of the concrete type, so the same logic can be reused for other types.
  • Templates are type-safe, but type checking happens late in compilation, so the debugged concrete version is kept as a reference.
  • A function template can deduce the argument types that a class template needs, so the types are not written out by hand.
  • A template is a general mechanism for compile-time programming: one definition, from which concrete code is produced for the types it is used with.

Related concepts

More concepts starting with F