Concepts / Template Compilation Basics

Template Compilation Basics

A C++ template lets you write the function once, and the compiler produces a version for each type the function is actually used with.

  • Programming

A Single Add Function for Many Types

Suppose you want one function that adds two values, whether they are integers, strings, or some type you wrote yourself. Writing a separate function for each type would repeat the same logic over and over. A C++ template lets you write the function once, and the compiler produces a version for each type the function is actually used with.

This article answers one question: how does the compiler decide whether a particular type is acceptable for a template? The course source calls the answer compile-time duck typing. Templates use duck typing, and the check happens when the code is compiled, not when the program runs.

What do you think happens?

Suppose a class Box has no addition operator defined. Will a call to the add template with two Box values compile?

  • Yes

  • No

Reveal answer
Answer:

No. The body of add uses a + b, and that operation is not defined for Box, so the compiler reports an error during compilation.

The template header does not say which types are allowed. The only thing that decides acceptance is whether the operations used in the body are available for the type.

Reading the Template as a List of Requirements

The way to judge a template is to read its body as a list of requirements rather than as a list of types. Consider this template. Its body contains one expression, a + b, and that expression names exactly one operation.

cpp
Notes on the code
  • The parameter type T appears in the signature, but nothing in the body says which types T may be.
  • The body assumes that whatever T is, a and b can be added together with +.

The signature mentions T, but the signature is not a list of allowed types. The only real requirement is hidden inside the body: the operation + must work on values of type T. Every type that the template is used with must provide that operation, and the compiler checks this for each type separately.

Tracing the Two Calls in main

Now call the template with two different types and follow what the compiler does for each. The example below uses int and std::string, and the same template body serves both.

cpp
Output
5
concat
Notes on the code
  • The first call instantiates add with T as int.
  • The second call instantiates add with T as std::string.
Worked example

Checking each call against the body

For each call to add, decide whether the template body is valid for the argument type.

  1. 1
    Call with int

    The body expression a + b is valid for int, so the compiler produces an int version of add.

  2. 2
    Call with std::string

    The string type provides concatenation through +, so the body expression a + b is valid, and the compiler produces a string version.

  3. 3
    What the name did not matter for

    Neither call required the template to know anything about the type's name. Only the operation mattered.

Result

Both calls are accepted because each type provides the single operation the body uses.

Where the Acceptance Decision Is Made

The decision about a type happens at the point where the compiler must check whether the body is valid for that argument. To judge a type in practice, read the template body, list the operations it needs, and check whether the type provides each one. Follow the argument from the call site, through the template body, to that check.

add(Box, Box) callInstantiate add withT as Boxa + b defined for BoxAccept and produceBox versionCompile-time erroruses T = Boxcheck bodyyesno
For a type passed to the add template, the compiler checks whether the operation used in the body is defined for that type, and accepts or rejects the type at that point.

The flowchart shows the rule in one place. The type is not compared against a list of approved names. The compiler asks one question about the body: is each operation it uses defined for this type? If the answer is yes for every operation, the type is accepted. If any operation is missing, the compiler reports an error during compilation.

Tracing the Box Failure Step by Step

The next case uses the same add template with a type it cannot handle. The point is not to memorize an error message but to see where the decision is made. Here the type is a class named Box, and the body's a + b is not defined for Box. Nothing about Box is wrong in itself. The template simply requires an operation that Box does not provide.

CallerCompileradd template bodycalls add with Boxinstantiate with T = Boxneeds a + bcompile-time error
When a caller instantiates add with Box, the messages pass from the call site to the compiler, then to the template body check, and back to the caller as a compile-time error.

Read the diagram from top to bottom. The caller's request is the start. The compiler instantiates the template for Box, and during that instantiation it checks the body's expression a + b against Box. That check fails, so the error is reported during compilation. The error is tied to the operation the body needs, which is why the fix belongs in Box, not in the call.

Before
struct Box {
    int value;
};

// add(Box{1}, Box{2}) fails to compile:
// no operator+ is defined for Box

Box has no operator+, so a + b in the body is not valid for Box and the call is rejected at compile time.

After
struct Box {
    int value;
};

Box operator+(Box lhs, Box rhs) {
    return Box{lhs.value + rhs.value};
}

// add(Box{1}, Box{2}) now compiles

Box provides operator+, so the same call is accepted and the compiler produces a Box version of add.

Duck Typing Compared with Declared Types

An interface-based check would ask whether a type is declared as one of the allowed kinds, for example by inheriting from a base class. A template works differently. It accepts a type based on the operations its body uses. The table below contrasts the two ideas.

QuestionDeclared-type acceptanceTemplate duck typing
What decides acceptance?A list of declared types or base classesThe operations the template body uses
Where is the check made?Against the declaration of the typeWhen the template is compiled for that specific type
What happens if a needed operation is missing?Depends on the declared interfaceA compile-time error is reported
Does the template header list allowed types?Usually yesNo; the header does not say which types are allowed

Declared-type acceptance compared with compile-time duck typing in templates.

Mistakes Beginners Make with Template Acceptance

Most mistakes come from two assumptions: that the template header lists the allowed types, and that a missing operation only fails at runtime. Both assumptions lead to the same error in reasoning, which is believing a type supports an operation when it does not.

  • Common mistake

    Assuming the template header lists the types the template accepts

    What it looks like

    Reading template <typename T> T add(T a, T b) and concluding that only built-in numbers are allowed.

    Why it fails

    The header only introduces the type parameter. It does not enumerate types. Acceptance depends on what the body requires.

    Do this instead

    Read the body and list each operation it uses, such as a + b, then check whether the type provides each one.

  • Common mistake

    Assuming a type supports + because it looks like a number or a string

    What it looks like

    Passing a custom Box value to add and expecting it to work because Box holds an int inside.

    Why it fails

    Box holds an int, but Box itself does not define operator+. The body's a + b applies to Box values, not to the int inside them.

    Do this instead

    Confirm that the type itself provides the operation, or add the needed operator to the type, as in the Box example.

  • Common mistake

    Expecting a missing operation to be found only when the program runs

    Why it fails

    The source states that the compiler checks each instantiation when the template is compiled for a specific type, so the unsupported operation is reported as a compile-time error.

    Do this instead

    Treat a compile error from a template as the compiler telling you the type lacks a required operation, and fix the type or the call.

Checking a Type Before You Use It

MEDIUM

A template function computes return a * b; for parameters a and b of type T. Without running any code, decide for each type whether the call should compile: int, std::string, and a class Box that defines no operator*. Then explain which step of the acceptance check decided each answer.

Hints
  • Start by listing the single operation the body uses, which here is a * b.
  • Ask whether the type itself provides that operation, not whether the type looks like a number or a string.
  • For a type that fails, identify the point where the compiler reports the error.

Key Takeaways

  • A template accepts a type based on the operations its body uses, not on a list of declared types. This is compile-time duck typing.
  • To judge whether a type works with a template, read the body, list the operations it needs, and check that the type provides each one.
  • The compiler checks each instantiation when the template is compiled for a specific type, so an unsupported operation is reported as a compile-time error.
  • Errors come from assuming the template header lists allowed types or that a missing operation only fails at runtime.
  • Fixing a rejected type means giving that type the needed operation, such as operator+ for Box, not changing the list of allowed names.

Related concepts

More concepts starting with T