Designing Generic Functions
In generic programming, a concept describes what a template argument must be able to do, and many different types can satisfy the same concept.
Where a Generic Function's Limits Live
Imagine a generic function named sum() that adds up the elements of something. Its template parameters look like open slots that will accept any type at all. Yet the function clearly cannot work with just any type. It needs a collection it can walk through, and it needs a numeric type it can add with. Those expectations are never written into the parameter list, but the function depends on them completely.
What do you think happens?
If a template parameter is declared as an unconstrained type, what kinds of types can a caller pass to it?
Reveal answer
Any type at all, as far as the parameter list is concerned.
The parameter list is an open slot. It does not say what the type must be able to do, so the real limits of the function live somewhere else: in the operations its body performs on that type.
The question to keep in mind as you read this article is simple: when a function is generic, where do its real limits live, if they are not written into its parameter list? The answer is in the concepts the function relies on.
What a Concept Is
A concept is a requirement, or set of requirements, that a type must satisfy to be used in a particular context.
Think of a concept as a job description rather than an employee. The job description says what a candidate must be able to do. Many different people can fit the same description, and the description is useful precisely because it can be checked against any candidate. In generic programming, a concept describes what a template argument must be able to do, and many different types can satisfy the same concept.
The source gives a set of familiar examples: integer, floating-point number, field, vector space, and container. The first two are ideas you already use in ordinary programming. The others come from mathematics and from the collections software is built around. Each one is a bundle of expectations rather than a single type. A container is not one specific class. It is any type that provides the behavior people expect of a container.
The Hidden Expectations Behind sum()
The sum() example makes the hidden concepts concrete. The function has two kinds of need. First, it needs something it can iterate over, a collection whose elements it can walk through one at a time. That requirement is the container concept. Second, it needs a value type it can add with, and that requirement is the number concept. Neither requirement appears as a name in the template parameter list. Each one sits behind a template argument and is only visible when you look at what the body does.
Tracing a Function's Uses to Find Its Concept
You can recover the hidden concept of any generic function by tracing what it does with each type parameter. The source offers a practical habit: for each type parameter, ask what the function does with values of that type. The answers reveal the concept, because the concept is the summary of those expected uses.
Reading a generic function's uses to name its concepts
A generic function takes a parameter of some type C and a parameter of some type T. Its body loops over the elements of the C argument, and inside the loop it adds each element into a running total of type T. What concepts does the function require?
- 1Trace the first type parameter
The body only ever iterates over the C argument. Following the habit in the source, the concept for C is about traversal, so C must be something that can be walked through.
- 2Name the concept for the first parameter
A type that can be walked through and offers the behavior expected of a collection satisfies the container concept. The parameter list only says C, but the concept says what C must be able to do.
- 3Trace the second type parameter
The body only ever adds values into the T total. The source says that if a function only adds values together, the concept is about arithmetic, so T must support addition.
- 4Name the concept for the second parameter
A type that supports the addition the body performs satisfies the number concept. Like the container, this requirement is invisible in the parameter list.
- 5Combine the requirements
The function requires two concepts, one for each type parameter. A caller who passes a type that is not a container, or a value type that cannot be added with, is outside what the function was written to handle.
The function requires a container concept for C and a number concept for T, even though its template parameter list names only two unconstrained types.
What do you think happens?
Suppose a function body only ever traverses its first type parameter and never adds values of that type. Which concept is that parameter about?
Reveal answer
Traversal
The source says that if the function only ever iterates over the parameter, the concept is about traversal. The expected use is the concept.
Why the Parameter List Falls Short
A template parameter list states what the caller may pass in, but it does not state what the function body depends on. A signature with unconstrained type names promises very little. The full concept includes every requirement the body actually relies on, including requirements the signature leaves out. The gap between the two is where the hidden expectations live.
Read the two columns as a contract question. The left column is what the signature says to the caller. The right column is what the body quietly needs. A caller who reads only the signature will believe that any type is acceptable, and that belief is wrong for this function.
What C++11 Can and Cannot Express
A natural question follows from the sum() example: if the function depends on these requirements, why not write them into the code? The source answers directly. Concepts cannot be expressed directly in C++11. The language lets the programmer pass types into a template, but it does not provide a direct way to declare that a template argument must be a container or must be a number.
This is why the programmer must understand the requirements. Since the language cannot state them, the burden falls on the author to work out the concept, name it, and make it clear to anyone who calls the function.
Where Concepts Come From
The source makes a claim worth pausing on: good, useful concepts are fundamental, and they are discovered more than they are designed. A programmer rarely invents a concept from nothing. The concept is already present in the field of application, and the work is to notice it, state it clearly, and write it down with enough precision for generic code to rely on it.
The source also notes that identifying and formalizing a concept to the degree needed for effective generic programming can be a real challenge. The phrase to the degree necessary matters. A concept does not have to capture everything about a type. It has to capture enough of the requirements that generic code can be written correctly against it.
Mistakes When Reading Generic Functions
Common mistake Treating the template parameter list as the complete contract
What it looks likeA reader sees two unconstrained type names and assumes any types will work.
Why it failsThe parameter list does not state what the body needs. The requirements live in the operations the body performs.
Do this insteadTrace what the function does with each type parameter and name the concept those uses imply.
Common mistake Assuming a concept is a single class
What it looks likeLooking for one specific container type and concluding that only that class can be used.
Why it failsA concept is a set of requirements. A container is any type that provides the behavior expected of a container.
Do this insteadDescribe the required behavior, not one implementation.
Common mistake Believing C++11 checks concepts for you
What it looks likePassing a type that has no addition and expecting the template parameter declaration to reject it.
Why it failsC++11 checks only that template arguments are types. It does not directly express that an argument must be a number.
Do this insteadDocument the concept informally and make sure the function's expectations are clear to callers.
Common mistake Confusing the job description of a concept with the employee who fits it
Practice Naming Hidden Concepts
A generic function takes a type parameter called Seq and a type parameter called V. Its body loops over the elements of Seq and, for each element, compares it with a value of type V to count matches. Name the concept each type parameter silently requires, and explain how you reached each answer.
Hints
- For each type parameter, ask what the body does with values of that type.
- Iteration over a parameter points to a traversal concept, and comparison points to a concept about equality or comparison behavior.
- Write the concept as a set of behaviors, not as a single class name.
Key Takeaways
- A concept is a requirement, or set of requirements, that a type must satisfy to be used in a particular context.
- A template parameter list names open slots, but the real limits of a generic function live in the concepts its body depends on.
- You can find a hidden concept by asking what the function does with values of each type parameter, since those expected uses summarize the concept.
- C++11 cannot express concepts directly and checks only that template arguments are types, so the programmer must understand and document the requirements.
- Good concepts are fundamental and are discovered more than designed, and they only need to capture enough requirements for generic code to be correct.