Standard Library Containers
Because the container choice and the element choice are independent, sum() is generic in two dimensions.
Why One Routine Should Serve Many Types
Suppose you have written a routine that adds up the values in a collection. Today the values are whole numbers, so you write it for int. Next week you need the same total for decimal measurements, and after that for a grid of numbers that represents a matrix. Copying and editing the routine each time works, but it duplicates logic and invites mistakes. Generic programming asks a different question: can one algorithm be written once and still be correct for many kinds of data?
What do you think happens?
Before reading on: if you need the same total for whole numbers, decimals, and matrices, what is the cost of copying and editing the routine each time?
Reveal answer
It duplicates logic and invites mistakes, even though it produces working code
The source says copying and editing the routine works, but it duplicates logic and invites mistakes. Generic programming aims to write the algorithm once and keep it correct for many kinds of data.
This article follows one thread from that question. First you will see what makes an algorithm generic, then how C++ templates support it, then what a container is, and finally how range checking protects you from asking a container for something it does not hold. Each stage builds on the one before it, so keep the question of valid inputs in mind throughout.
Requirements, Not Lists of Allowed Types
Generic programming designs general algorithms that accept a wide variety of types, provided those types meet the algorithm's requirements on its arguments.
The word that carries the most weight in that definition is requirements. Generic does not mean that every type is automatically acceptable. It means the algorithm is written in terms of what it needs from its inputs rather than in terms of one specific type. If a type supplies what the algorithm needs, the same algorithm body can be used with that type. If it does not, the algorithm's requirements are unmet, and the type is not a valid argument.
Templates and Compile-Time Parametric Polymorphism
In C++, the template is the main language feature that supports generic programming. A template lets you write an algorithm with a type that is not yet fixed. The algorithm is then used with particular concrete types. The source describes this as compile-time parametric polymorphism: one piece of code is written in a parameterized form, and the parameter, which is a type, is supplied when the code is used.
Compile-time parametric polymorphism means one algorithm is written with a type parameter, and the concrete type is chosen during compilation rather than while the program runs.
The adjective compile-time tells you when this happens. The choice of type is part of what the compiler works with, not something decided while the program runs. Because of this, one algorithm definition can serve many valid container and element combinations, and each combination that is used is prepared for that particular type when the code is compiled.
The diagram shows the relationship in words: the algorithm is defined once, and each element type that meets its requirement can be used with it. Notice that the algorithm does not change between these uses. Only the type it is applied to changes.
Tracing sum() Across Two Dimensions
The source uses sum() as its main illustration. The sum() algorithm is generic in two dimensions at once, and each dimension can vary independently while the algorithm itself stays the same. The worked example below follows the reasoning for each dimension.
Checking whether sum() accepts a new combination
Could sum() be used on a list of double values, given that a list supports begin() and end() and double can be added to the Value argument?
- 1Check the container requirement
sum()works on any data structure that supportsbegin()andend(). A list is among the standard-library structures that qualify, so the container requirement is met. - 2Check the element requirement
The element type must be addable to the Value argument. double is one of the element types the source names as valid, so the element requirement is met.
- 3Decide without rewriting the algorithm
Both requirements are satisfied, so the same
sum()body applies. No separatesum()is needed for lists of double.
sum() is valid for a list of double, and the algorithm itself is unchanged.
Notice what this example did not require. It did not need a separate sum() for vectors of int, another for lists of double, and another for matrices. The algorithm's requirements were the only thing that mattered, and those requirements are stated in terms of what the container and the element must support.
What a Container Holds
A container is an object that holds elements, and each element is one of the items it holds.
Many programs need to hold more than one value at a time. A list of student scores, a set of file names, or a queue of tasks all share the same need: a single object that holds a group of related items. A container is best understood as an object whose job is to hold other things. Those other things are its elements. The container is the outer thing, and the elements are the items inside it. One container can hold many elements, and the relationship is one to many.
Read the diagram from top to bottom. The container sits at the top, and each element hangs beneath it as one of the things it holds. Elements are what a container holds, which is why the two ideas are introduced together in the source.
Range Checking as a Read-Only Question
Range checking confirms that a requested position falls within the range of elements a container actually holds.
The reason this check matters is that a request for a position is only useful when that position exists. Asking a container for something beyond the elements it holds is asking for something that is not there. Range checking is the habit of verifying the request before relying on its result.
Because out-of-range requests are cases you must handle, you should not assume the element exists. The check gives you the information you need to decide what to do next.
Tracing a Range Check Step by Step
The example below is an original illustration, not taken from the source. It uses a container that holds five elements and numbers them 1 through 5 for the purpose of this example. The source does not fix a numbering scheme, so the numbers here are only a teaching device. The reasoning follows three steps: identify the range, compare the request against it, and decide.
Checking a position of 7 in a five-element container
A container holds five elements, numbered 1 through 5. A caller requests position 7. Does the range check pass?
- 1Identify the range
The container holds elements at positions 1 through 5. Those are the only positions that refer to elements it actually holds.
- 2Compare the request against the range
Position 7 is greater than 5, the position of the last element. It lies beyond the end of the range.
- 3Decide
Because 7 falls outside the range of held elements, the range check fails. The request must be handled as an out-of-range case. The container itself is unchanged.
The range check fails. Position 7 is not valid for this container.
Now change the request to a position inside the range. Take position 3. The same three steps apply: identify the range of positions 1 through 5, compare 3 against it, and decide. The comparison shows that 3 is at least 1 and at most 5, so the range check passes. The check did not change the container. It only answered whether the request pointed at something the container really holds.
What do you think happens?
Using the same five-element container, will a request for position 0 pass the range check?
Reveal answer
No, because 0 is below the lowest held position of 1
Under this example's numbering, the held positions are 1 through 5. Position 0 is less than 1, so it fails the lower-boundary comparison in the flowchart and is rejected.
Mistakes With Containers and Ranges
Common mistake Assuming any type can be passed to a generic algorithm
What it looks likePassing a type that cannot be added to the Value argument to sum() and expecting it to work.
Why it failsGeneric does not mean every type is automatically acceptable. The algorithm's requirements must be met.
Do this insteadCheck whether the element type supports what the algorithm needs before using it.
Common mistake Believing a separate algorithm is needed for each element type
What it looks likeWriting one sum() for int, another for double, and another for Matrix.
Why it failsThe algorithm's requirements are what matter. The same body applies to any type that meets them.
Do this insteadWrite the algorithm once in terms of its requirements and let the type parameter vary.
Common mistake Thinking the type choice for a template is made while the program runs
Why it failsTemplates provide compile-time parametric polymorphism, so the concrete type is tied to compilation.
Do this insteadTreat the chosen type as part of what the compiler works with.
Common mistake Treating a container as if it were one of its elements
Why it failsA container is the outer object that holds elements. The elements are the items inside it.
Do this insteadKeep the container and its elements distinct when you reason about a request.
Common mistake Expecting a range check to fix or remove an out-of-range request
Why it failsRange checking only reads the container's current contents. It does not change them.
Do this insteadWhen a check fails, handle the out-of-range case explicitly rather than assuming the element exists.
Checking Your Own Range Requests
A container holds eight elements numbered 1 through 8. For each requested position, say whether the range check passes or fails: 0, 4, 8, and 9. Work through the three steps for each one: identify the range, compare the request against it, and decide.
Hints
- The held positions run from 1 to 8, so a valid request must be at least 1 and at most 8.
- Position 0 fails the lower comparison, and position 9 fails the upper comparison.
- The range check never changes the container, so the answers do not depend on any earlier request.
Explain in your own words why sum() does not need a new definition for a list of double, even though the source describes the element type as independent of the container type.
Hints
- Ask which two requirements
sum()places on its arguments. - Think about whether either requirement mentions int specifically.
Key Takeaways
- Generic programming writes an algorithm once in terms of its requirements, so it can be correct for any type that meets those requirements.
- In C++, templates provide compile-time parametric polymorphism: the concrete type is supplied and chosen during compilation.
sum()is generic in two dimensions, the container type (which must supportbegin()andend()) and the element type (which must be addable to the Value argument).- A container is an object that holds elements, and range checking confirms that a requested position falls within the elements it actually holds without changing the container.