Templates

Function Templates

A template is a pattern the compiler uses to expand into actual code. Rather than writing out a completed function yourself, you write a general outline containing placeholders for types. Later, when someone calls this outline with actual types, the compiler fills in the blanks and generates a full, concrete function. The original pattern is never actually called; only the generated instances are.

This idea is useful the instant you need to use the same logic for more than one type. Consider a simple function that returns the larger of two values. For integers, this would look like:

#include <iostream>

int max(int x, int y)
{
    // Return the computed value to the caller
    return (x < y) ? y : x;
}

int main()
{
    std::cout << max(7, 3) << '\n';
    return 0;
}

The function takes two ints, x and y. The expression (x < y) determines which of them is larger, which is then returned. The call in main therefore prints 7.

Floating point numbers are compared using exactly the same expression, but the argument types have to change.

Writing a second version by hand requires creating an almost identical copy of the previous version of the source.

#include <iostream>

int max(int x, int y)
{
    // Choose between the two expressions using this condition
    return (x < y) ? y : x;
}

double max(double x, double y)
{
    return (x < y) ? y : x;
}

int main()
{
    std::cout << max(7, 3) << '\n';
    std::cout << max(7.5, 3.2) << '\n';
    return 0;
}

Now we have two separate functions that are generated by the compiler according to the argument type. The body of the function remains exactly the same; only the type names are changed. To support yet another type, we would have to make another copy of the same body of source. This is the exact problem that a function template solves.

The function template writes the single copy of the pattern.

#include <iostream>

// Make the following definition depend on template arguments
template <typename T>
T max(T x, T y)
{
    return (x < y) ? y : x;
}

int main()
{
    std::cout << max(7, 3) << '\n';
    std::cout << max(7.5, 3.2) << '\n';
    return 0;
}

The first line, template <typename T>, is the template parameter declaration. The compiler knows from this line that it is reading a pattern, not a function, and that T is intended to stand for whatever type a caller of the function might provide. Inside the template, the return type and both arguments are specified as type T. The function body itself is unchanged from the previous version.

The compiler reaches the call max(7, 3), which passes two arguments of type int. It uses this information to deduce that it should generate a version of the max function in which T is replaced by int. The generated function is identical to the handwritten integer version shown above. It is this generated version that the call is actually translated into, and which therefore returns 7.

The call max(7.5, 3.2) uses two arguments of type double. In this case, the compiler substitutes double in place of T to create a second version of the function. Calls to the function that pass any given type are translated to only one instance, and calls that pass other specific types are translated to other specific instances. There is no sharing of code or data between generated instances.

The template is only a pattern until it is used, meaning it takes up no space in the executable until that time. The first time a specific type (or combination of types) occurs, the compiler creates the associated instance and reuses it on every subsequent call that uses the same type(s).

The name T is a conventional name for a single unconstrained type parameter, but any identifier could be used instead. The keyword typename simply indicates to the compiler that the identifier is a type name, although the older class keyword is also legal and used synonymously.

Template arguments are restricted by the operations performed within the body of the template. The max template function uses the less than operator (<), so only arguments for which the less than operator is defined are allowed to be passed to it. The compiler will refuse to generate the function instance if the provided type does not support the required operations, printing an error to the user that can only occur at a call site.

The same approach works with other operators and functions. This template simply prints any value passed to it:

#include <iostream>

template <typename T>
void print(T value)
{
    std::cout << value << '\n';
}

int main()
{
    print(42);
    print(3.14);
    print('X');
    return 0;
}

In the first call, the compiler instantiates T to be int to create the appropriate version of the function. In the second, it instantiates T to be double, and in the third to char. In each case, the compiled function only contains the required insertion to standard output followed by a newline. Since the insertion operator is defined for these types, every instance succeeds.

Template argument deduction is the mechanism that allows you to omit the explicit type when calling a template function. When the compiler sees print(42), it inspects the argument, figures out that it is of type int, and deduces that T can only be int. You can still explicitly state the type by calling print<int>(42), but it is not required since the compiler can deduce the information on its own.

The deduction only occurs when the compiler has exactly one possible way to deduce the type. If you pass two arguments of different types, such as max(7, 3.2), the compiler cannot know which type T is supposed to represent and will result in a compilation error unless you explicitly provide the desired type.

Exercise: Function TemplatesStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: Function Templates

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Replace the duplicated max overloads with a single function template and add a same-type char call for A and Z. Predict all three return values.

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <iostream>

int max(int x, int y)
{
    // Choose between the two expressions using this condition
    return (x < y) ? y : x;
}

double max(double x, double y)
{
    return (x < y) ? y : x;
}

int main()
{
    std::cout << max(7, 3) << '\n';
    std::cout << max(7.5, 3.2) << '\n';
    return 0;
}

Hints

Hint 1

The two max overloads repeat the same comparison for int and double. A template can replace those concrete parameter and return types with one T.

Hint 2

Write template <typename T> T max(T x, T y) with the same body, then call it with two values of one type. Mixed argument types need a separate deduction decision.

Worked solution

One function template replaces the duplicated int and double overloads. The added char call instantiates it for char and chooses Z over A. All calls provide two values of the same type so deduction is unambiguous.

#include <iostream>
template <typename T>
T max(T x, T y)
{
    return (x < y) ? y : x;
}
int main()
{
    std::cout << max(7, 3) << '\n';
    std::cout << max(7.5, 3.2) << '\n';
    std::cout << max('A', 'Z') << '\n';
}

Example output

7
7.5
Z

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.