Concepts / The Standard Template Library in C++

The Standard Template Library in C++

C++ is strongly typed, so the run-time style of generic programming used in Smalltalk is not possible in C++, but C++ has its own powerful generic mechanism based on templates.

  • Programming

Predicting Where a Generic Error Appears

Generic programming is the idea of writing one piece of code that works on data of many different types. Learners who have seen this idea in a dynamically checked language often carry a hopeful assumption into C++: that a single generic subroutine will simply accept any kind of data and sort it, with any problems discovered only when the program runs. This article corrects that assumption. C++ does have a powerful and flexible form of generic programming, but it works on different principles, and those principles change where and when errors appear.

What do you think happens?

A generic C++ template compares two values with the > operator. You call it with a type called Card, and Card has no definition for >. When does the error appear?

  • At run time, when the comparison is first executed

  • At compile time, before the program runs

  • Nowhere, because the program silently treats the comparison as false

Reveal answer
Answer:

At compile time, before the program runs

The compiler tries to generate the subroutine from the template for the type supplied. Generation succeeds only if the template's operations make sense for that type. If > is not defined for Card, generation fails and the failure is reported at compile time rather than the program crashing at run time.

Commit to an answer before reading on. The reasoning behind the correct answer is what makes the difference between the Smalltalk style of generic programming and the C++ style visible.

Why Run-Time Generics Do Not Carry Over

The run-time style of generic programming used in Smalltalk cannot be used in C++ because C++ is a very strongly typed language. Every variable has a type and can hold only data values of that type. A variable declared to hold integers cannot quietly hold a string later on. The kind of generic programming that depends on that flexibility is therefore impossible in C++.

The contrast between the two styles is easiest to see by asking when each one makes its type decisions. The diagram below sets the two approaches side by side.

One genericsubroutineAccepts values of any typeOne templatepatternPlaceholder for a typeType checked duringexecutionProblems found when theprogram runsCompiler generates aversion per typeDone before executionOperation fails atrun timeError appears duringexecutionError reported atcompile timeDetected before the programrunsused with a valueif operation missingused with a typeif operation missing
When are type decisions made in run-time generic programming compared with C++ templates, and what happens at each stage?

Strong Typing Moves the Check Earlier

Strong typing does not remove generic programming from C++. It changes the mechanism. Instead of one subroutine that works with values of any type at run time, C++ provides a way to write a single pattern and let the compiler produce a concrete version for each type that is actually used.

Everything needed to produce a version of a template is known when the program is compiled. Because of this, the compiler can detect a type that does not support a required operation before the program is executed. Strong typing is the reason the check can happen this early: since each value has a fixed type, the compiler can know in advance which operations will be applied to it.

Key point

Strong typing does not make C++ less generic. It makes the type decisions happen during compilation, so mistakes about types surface before the program runs.

A Template as a Factory for Subroutines

Definition

A template is a pattern, or factory, for making subroutines and classes. The compiler generates a specific version for each type that is actually used. A template is not itself a subroutine that runs. It describes how a subroutine should look, with a placeholder standing in for the type of the data.

The source uses a helpful comparison: a factory does not itself perform the work of its products. It holds a design and produces concrete items from that design when asked. A subroutine template works the same way. The design is the template; the products are the concrete subroutines the compiler generates from it.

Generating Versions for int and Card

The source describes the compiler's job with a simple sorting example. When a program calls sort on an array of ints, the compiler uses the template to generate a subroutine for sorting arrays of ints. When the same template is called on an array of Card objects, the compiler generates a separate subroutine for sorting arrays of Cards. One template feeds two distinct generated versions, each chosen by the type of the data in the call.

Sort templatePlaceholder ItemTypeCall with int arrayType supplied: intCall with Card arrayType supplied: CardGenerated int sortSorts arrays of intGenerated Card sortSorts arrays of Cardinstantiated for intinstantiated for Cardcompiler generatescompiler generates
How does a single template with a type placeholder become several distinct compiled subroutines, one for int and one for Card, before the program runs?
Worked example

Tracing the Two Calls to One Template

A program contains one sort template and makes two calls: one on an array of ints and one on an array of Card objects. How many subroutines does the compiler produce, and what decides which version each call uses?

  1. 1
    Identify the template

    There is one template, the pattern that contains the placeholder ItemType. It is written once in the source.

  2. 2
    Identify the type in each call

    The first call supplies int as the type for ItemType. The second call supplies Card.

  3. 3
    Generate a version per type

    The compiler generates a subroutine for sorting arrays of ints and a separate subroutine for sorting arrays of Cards.

  4. 4
    Check the required operations

    Generation succeeds for each type only if the template's operations, such as >, are defined for that type. Both int and Card are assumed to qualify in this example.

Result

Two distinct subroutines are produced at compile time, one per type. The type in each call decides which generated version is used.

Operators Decide Whether a Template Fits

The compiler tries to generate a subroutine from a template, and it succeeds only if the template's operations make sense for the type supplied. The template in the earlier example compares values with >. If that operator is defined for a type, generation succeeds. If it is not, generation fails, and the failure is reported at compile time.

In C++ it is possible to write definitions of operators like > for any type. A programmer can therefore supply a meaning for > that applies to Card values. The template that depends on > can then be used with Cards. This is operator overloading: a user-defined type such as Card supplies the operators a template needs.

Template uses >Required operation:greater-thanIs > defined for thetype?For example, CardGeneration succeedsSubroutine is producedCompile-time failureReported before executioninstantiate with typeyesno
What happens when a template that uses > is instantiated with a type that defines >, and what happens when that type does not?
Key point

The success of a template is a property of the combination of template and type. The template does not guarantee anything on its own. It asks for certain operations, and a type qualifies only if it provides them.

Class Templates Generate Whole Classes

Templates are not limited to subroutines. The source explains that C++ also has templates for making classes. A programmer can write a template for a binary tree class, and that single template can generate classes for binary trees of ints, binary trees of strings, binary trees of dates, and so on. The reasoning is the same as for subroutines: one pattern, many concrete classes, each produced for a particular type.

In the real world

Consider a binary tree template. Once it exists, a program that needs a tree of integers and another that needs a tree of dates both draw on the same pattern. The compiler produces one concrete tree class for each type actually used, so a tree of dates never needs its own hand-written class.

What the Standard Template Library Provides

All of the mechanisms described above are available to programmers in a large collection that comes with modern C++. The source calls this collection the Standard Template Library, or STL. It is described as a large number of pre-written templates. The STL is a body of ready-made generic building blocks, so a programmer does not need to write common templates from scratch.

The source is candid that the STL is quite complex, and that many people would say it is much too complex. It also notes that the STL is one of the most interesting features of C++. A reasonable way to approach it is to understand the template mechanism first, since the STL is built from exactly those templates.

Mistakes Learners Make with Templates

  • Common mistake

    Expecting one generic subroutine to accept any data and fail only at run time

    Why it fails

    C++ generates a concrete version for each type actually used, and the compiler checks that the required operations exist during compilation.

    Do this instead

    Think of the template as a factory. Ask which types are supplied, then check whether each type provides the operations the template needs.

  • Common mistake

    Believing the keyword class in template class ItemType means only user-defined classes are allowed

    Why it fails

    The source notes that the placeholder can be replaced by any type, including primitive types such as int.

    Do this instead

    Read the keyword as declaring a type placeholder, not as a restriction to classes.

  • Common mistake

    Assuming a template works with any type because it compiled once

    Why it fails

    Whether generation succeeds depends on the combination of template and type. A type that lacks a required operator will not qualify.

    Do this instead

    Check that each type used with the template defines the operators the template depends on, such as >.

  • Common mistake

    Assuming a template is itself a runnable subroutine rather than a pattern for making subroutines

Practice Tracing a Template

MEDIUM

A template for finding the larger of two values uses the > operator. A programmer calls it with ints, with doubles, and with a user-defined type called Card. Card defines no meaning for >. For each call, state whether the compiler generates a subroutine, and explain at which stage any problem would be reported.

Hints
  • Ask first which type each call supplies for the placeholder.
  • Ask whether the required operator is defined for that type.
  • Remember that the success of generation depends on both the template and the type.
EASY

Explain, in your own words, why C++ can detect a missing operator before the program runs, even though the program has not yet been executed.

Hints
  • Connect the answer to strong typing and to what the compiler knows during compilation.

Key Takeaways

  • C++ is strongly typed, so the run-time style of generic programming used in Smalltalk is not possible; C++ uses templates instead.
  • A template is a pattern, or factory, for making subroutines and classes, and the compiler generates a specific version for each type actually used.
  • A template's required operations, such as >, must be defined for the type supplied; if they are not, the failure is reported at compile time rather than at run time.
  • Operator overloading lets a user-defined type such as Card supply the operators a template needs.
  • The Standard Template Library is a large collection of pre-written templates that comes with modern C++, and understanding the template mechanism is the best foundation for it.