Why Function Templates Are Needed

If several functions differ only in their parameter types, their common implementation can be moved into a template.

For example, the following function returns the greater of two int values:

int max_value(int a, int b)
{
    return b < a ? a : b;
}

A separate overload would be required for values of type double:

double max_value(double a, double b)
{
    return b < a ? a : b;
}

Versions for float, long, std::string, and other types may be needed as well. The code would remain almost identical.

A function template makes it possible to write the common implementation once:

template<typename T>
T max_value(T a, T b)
{
    return b < a ? a : b;
}

The same implementation can now be used with different types:

max_value(10, 20);
max_value(3.5, 7.2);

In the first call, T is deduced as int; in the second, it is deduced as double.

Type Parameter

The declaration

template<typename T>

declares a template parameter named T.

You can then use T wherever a type would normally be specified:

template<typename T>
T max_value(T a, T b)
{
    return b < a ? a : b;
}

In this template, T is used:

  • as the return type;
  • as the type of parameter a;
  • as the type of parameter b.

The name T is a convention and has no special meaning in the language. The parameter can be given a more descriptive name:

template<typename ValueType>
ValueType max_value(ValueType a, ValueType b)
{
    return b < a ? a : b;
}

Both declarations are equivalent.

For short templates, the name T is commonly used. When there are several parameters, names such as T1, T2, or more descriptive names can be used.

Template Parameters and Function Parameters

In the declaration

template<typename T>
T max_value(T a, T b);

T, a, and b belong to different levels.

  • T is a template parameter that specifies a type;
  • a and b are function parameters that receive values when the function is called.

For example, in the call

max_value(10, 20);

the compiler deduces:

T = int

When the function executes, its parameters receive specific values:

a = 10
b = 20

Template parameters can represent more than just types. For example:

template<typename T, std::size_t N>

Here, T is a type parameter, while N is a compile-time value, also called a non-type template parameter.

Template Instantiation

The definition of a template

template<typename T>
T max_value(T a, T b)
{
    return b < a ? a : b;
}

defines the general form of a function.

When the compiler encounters the call

max_value(10, 20);

it deduces:

T = int

and instantiates a function for int when needed.

In simplified form, the resulting function can be represented as follows:

int max_value(int a, int b)
{
    return b < a ? a : b;
}

For another call,

max_value(3.5, 7.2);

a version with T = double is required:

double max_value(double a, double b)
{
    return b < a ? a : b;
}

These functions are not declared manually in the source code. They are produced from the template when it is used.

Several calls with the same type use the same specialization:

max_value(1, 2);
max_value(10, 20);
max_value(100, 200);

All of them use:

max_value<int>

Template Argument Deduction

In most cases, you do not need to specify the type explicitly:

max_value(10, 20);

The compiler determines T from the function arguments.

The type can also be specified explicitly:

max_value<int>(10, 20);

Template Parameter and Template Argument

In the declaration

template<typename T>

T is called a template parameter.

In the call

max_value<int>(10, 20);

int is a template argument. It explicitly specifies:

T = int

In an ordinary call,

max_value(10, 20);

the same template argument is deduced automatically.

The detailed rules of this process are called template argument deduction.

typename and class

A type parameter can be declared in two ways:

template<typename T>

or:

template<class T>

For a type parameter, these forms are equivalent:

template<class T>
T max_value(T a, T b)
{
    return b < a ? a : b;
}

For example, the call

max_value(10, 20);

gives:

T = int

And the call

max_value(1.5, 2.5);

gives:

T = double

Historically, class was used to declare type parameters before typename was introduced. Both forms remain part of the language.

In modern code, typename is often used because it more clearly communicates that the parameter represents an arbitrary type.

Requirements for a Type

The presence of typename T does not mean that a template can be applied to every type.

Consider this function:

template<typename T>
T max_value(T a, T b)
{
    return b < a ? a : b;
}

Its body contains the expression:

b < a

Therefore, the selected type T must support an appropriate < operation.

Everything works correctly with int:

max_value(10, 20);

The template also works with double:

max_value(1.5, 2.5);

Lexicographical comparison is defined for std::string:

std::string a = "alpha";
std::string b = "beta";

auto result = max_value(a, b);

Now consider a user-defined type:

struct Point
{
    int x;
    int y;
};

Trying to call max_value with two objects of this type looks like this:

Point a{1, 2};
Point b{3, 4};

auto p = max_value(a, b);

During instantiation, the compiler needs the expression:

b < a

However, operator< is not defined for Point. Therefore, this specialization does not compile.

How Operations Define Requirements

Requirements for a type follow from the operations used in a template.

For example:

template<typename T>
T add(T a, T b)
{
    return a + b;
}

This template requires the following expression to be valid for type T:

a + b

The following function requires support for the increment operation:

template<typename T>
void increment(T& value)
{
    ++value;
}

For the value-printing template

template<typename T>
void print(const T& value)
{
    std::cout << value;
}

an appropriate operator<< must exist.

Since C++20, these requirements can be expressed explicitly with concept and requires. It is best to move on to them after learning the basic mechanics of templates.

Templates Work at Compile Time

Consider a simple template:

template<typename T>
void foo(T value)
{
}

When it is called as follows:

foo(42);

the compiler already knows:

T = int

and works with a concrete specialization:

foo<int>

For the call

foo(3.14);

it uses:

foo<double>

Type selection happens at compile time.

This distinguishes templates from classic runtime polymorphism based on virtual functions:

struct Shape
{
    virtual void draw() = 0;
};

With a virtual call, the specific implementation can be determined while the program is running. In template code, the concrete types are known to the compiler in advance.

As a result, template code is generally well suited for optimization: after instantiation, the compiler sees an ordinary function with concrete types.

Complete Example

One Template for Numbers and Strings

#include <iostream>
#include <string>

template<typename T>
T max_value(T a, T b)
{
    return b < a ? a : b;
}

int main()
{
    std::cout << max_value(10, 20) << '\n';
    std::cout << max_value(4.2, 7.5) << '\n';

    std::string s1 = "alpha";
    std::string s2 = "beta";

    std::cout << max_value(s1, s2) << '\n';
}

The program uses three versions of max_value:

max_value<int>
max_value<double>
max_value<std::string>

The original implementation remains just one:

template<typename T>
T max_value(T a, T b)
{
    return b < a ? a : b;
}

Key Points to Remember

  • A function template lets you write a common implementation once and apply it to different types.
  • T in template<typename T> is a type parameter.
  • A template parameter specifies a type, while function parameters receive values.
  • Creating a concrete function from a template is called instantiation.
  • A template argument can be specified explicitly or deduced by the compiler.
  • typename and class are equivalent when declaring a type parameter.
  • A template only works with types that support the required operations.
  • Type selection and template instantiation occur at compile time.