Concepts / Designing Generic Functions

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.

  • Programming

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?

  • Only types that the function author lists by name

  • Any type at all, as far as the parameter list is concerned

  • Only built-in numeric types

Reveal answer
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

Definition

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.

sum()generic functioncollection parametersomething it can walkthroughcontainer conceptany type with containerbehaviorvalue parametera type it can add withnumber conceptany type that can be addedtakestakesmust satisfymust satisfy
Each template argument of sum() silently demands a concept. The parameter list names the slots, while the concepts describe what must be true of whatever fills them.

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.

Worked example

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?

  1. 1
    Trace 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.

  2. 2
    Name 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.

  3. 3
    Trace 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.

  4. 4
    Name 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.

  5. 5
    Combine 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.

Result

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?

  • Arithmetic

  • Traversal

  • The parameter has no concept at all

Reveal answer
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.

typename Cany type acceptedcontainer concepttraversal behavior requiredtypename Tany type acceptednumber conceptaddition behavior requiredbody relies onbody relies on
The template signature promises only that each argument is a type. The concept adds the operations and behavior the function body depends on, which the signature does not state.

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 like

    A reader sees two unconstrained type names and assumes any types will work.

    Why it fails

    The parameter list does not state what the body needs. The requirements live in the operations the body performs.

    Do this instead

    Trace 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 like

    Looking for one specific container type and concluding that only that class can be used.

    Why it fails

    A concept is a set of requirements. A container is any type that provides the behavior expected of a container.

    Do this instead

    Describe the required behavior, not one implementation.

  • Common mistake

    Believing C++11 checks concepts for you

    What it looks like

    Passing a type that has no addition and expecting the template parameter declaration to reject it.

    Why it fails

    C++11 checks only that template arguments are types. It does not directly express that an argument must be a number.

    Do this instead

    Document 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

MEDIUM

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.

Related concepts

More concepts starting with D