What template argument deduction is

Earlier, we used the following function template:

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

It can be called without specifying T explicitly:

max_value(10, 20);

The compiler determines the type automatically:

T = int

This process is called template argument deduction.

How T is deduced

Consider a simpler template:

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

In the following call:

foo(42);

the argument has type int, so the compiler determines: T = int

Other arguments produce different results:

foo(42);       // T = int
foo(3.14);     // T = double
foo(3.14f);    // T = float
foo('A');      // T = char

The compiler matches the function parameter type T

against the type of the supplied argument and uses it to determine the template parameter.

After the call

foo(42);

you can think of the subsequent work as involving the following specialization:

foo<int>

After the call

foo(3.14);

the following specialization is used:

foo<double>

One template parameter can appear multiple times

In max_value, the same T is used for both parameters:

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

In the following call:

max_value(10, 20);

deduction is performed for each argument:

10 → T = int
20 → T = int

Both results agree. The compiler therefore determines:

T = int

and can use the following specialization:

max_value<int>

The same thing happens here:

max_value(1.5, 2.7);

Both arguments have type double:

1.5 → T = double
2.7 → T = double

The result is:

max_value<double>

A problem arises when the arguments produce different results while deducing the same template parameter.

Type conflict in max_value(4, 7.2)

Consider the following call:

max_value(4, 7.2);

The first argument has type int:

4 → T = int

The second argument has type double:

7.2 → T = double

However, the template uses only one parameter named T:

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

The parameter T must represent a single type, while the arguments suggest two different types: int and double.

In the call max_value(4, 7.2), the first argument produces T = int, while the second produces T = double. Because the deduction results must agree, the call results in an error.

max_value(4, 7.2); // error

Why a regular function works in this case

Suppose there is a regular function:

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

The following call is valid:

max_value(4, 7.2);

The function parameter types are already known:

double max_value(double, double);

The first argument is therefore converted from int to double:

4 → 4.0

The resulting call is:

max_value(4.0, 7.2);

With the following template:

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

the compiler must first determine T. In the call

max_value(4, 7.2);

this step produces a conflict:

T = int
T = double

The error occurs during the deduction of T, so the compiler never reaches the stage where it selects a specific specialization and applies implicit argument conversions.

Specifying a template argument explicitly

The parameter T can be specified manually:

max_value<double>(4, 7.2);

The type of T is now known explicitly:

T = double

After substitution, the function parameters become:

double max_value(double a, double b);

An ordinary conversion is applied to the first argument:

int → double

The call is therefore valid.

The conversion can also be performed in the other direction:

max_value<int>(4, 7.2);

The function parameters now have type int, so the value 7.2 is converted to int:

7.2 → 7

The call compiles, but the fractional part is discarded.

Type deduction and default function arguments

The type T is deduced from the supplied function arguments, while a default argument is applied later. Therefore, the default argument does not participate in template argument deduction.

For example:

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

The following call works:

foo(42);   // T = int

The type T is deduced from the supplied argument 42.

The call foo() supplies no arguments, so the compiler has no information from which to deduce T:

foo();     // error: T cannot be deduced

The default value for value is applied only after the template parameters have been determined. The compiler must first determine T, form a specific function, and only then use the function's default argument.

The type can be specified explicitly:

foo<int>();   // OK

Now the compiler knows:

T = int

and the function effectively has the following parameter:

int value = {};

Another option is to provide a default value for the template parameter itself:

#include <string>

template<typename T = std::string>
void foo(T value = "")
{
}

Both calls are then valid:

foo(42);   // T = int
foo();     // T = std::string

In the first case, T is deduced from the function argument, so the default template argument is not used. In the second case, no function arguments are supplied, so the compiler uses the default type std::string specified for T.

It is important to distinguish between these two independent mechanisms:

template<typename T = std::string> // default argument for the template parameter
void foo(T value = "");            // default argument for the function parameter

Converting the type before the call

Another option is to convert the arguments to the same type before calling the function template:

max_value(static_cast<double>(4), 7.2);

The deduction mechanism now sees two arguments of type double:

double
double

It therefore determines:

T = double

The conversion can also be performed in the opposite direction:

max_value(4, static_cast<int>(7.2));

In that case:

T = int

The first option is usually more appropriate for numbers with a fractional part because it preserves that part.

Deduction requires a consistent type

For the following call:

max_value(4, 7.2);

the following reasoning may seem logical:

int and double
→ double can represent both values
→ choose double

However, template argument deduction works differently:

first argument  → T = int
second argument → T = double
                   ↓
                conflict

Each argument participates in deduction independently, and the resulting types must agree. The compiler does not choose the “best” or widest type instead.

If the function genuinely needs to accept different types, the template itself must reflect that requirement:

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

The first and second arguments are now associated with different template parameters:

T1 = int
T2 = double

This version requires a separate discussion because the return type and the rules for comparing values of different types must also be considered.

Automatic deduction and explicit type selection

The difference between automatic deduction and explicitly specifying a type can be seen with a simple template:

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

Automatic deduction

foo(42);

The compiler determines:

T = int

Explicit type selection

foo<double>(42);

The type is specified in advance:

T = double

The value 42 is then converted to double.

The calls therefore use different specializations:

foo(42);          // foo<int>
foo<double>(42);  // foo<double>

The original argument value is the same, but the resulting specializations are different.

Deduction uses argument types

Type deduction usually depends on the argument's type rather than its value:

foo(1);
foo(100);
foo(-500);

In every case:

T = int

The following calls produce different specializations:

foo(1);     // T = int
foo(1L);    // T = long
foo(1.0);   // T = double
foo(1.0f);  // T = float

Literal suffixes matter because they change the type of a literal.

For example, the literal

1.0

has type double, while the literal

1.0f

has type float. Therefore:

foo(1.0);   // foo<double>
foo(1.0f);  // foo<float>

What happens when a function template is called

For the following call:

max_value(10, 20);

the process can be represented in simplified form as follows:

max_value(10, 20)

10 → int
20 → int

T = int

max_value<int>

For the call

max_value(4, 7.2);

a conflict occurs:

4   → int
7.2 → double

T = int
T = double

deduction failed

In the call with an explicitly specified type

max_value<double>(4, 7.2);

T is already known, so the compiler immediately obtains the specific function parameter types:

T = double

function parameters:
double, double

4 → 4.0

max_value<double>(4.0, 7.2)

Complete example

Automatic and explicit type selection

#include <iostream>

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(1.5, 2.7) << '\n';

    // Error:
    // std::cout << max_value(4, 7.2) << '\n';

    std::cout << max_value<double>(4, 7.2) << '\n';

    std::cout << max_value(
        static_cast<double>(4),
        7.2
    ) << '\n';
}

In the first two calls, the template argument is deduced automatically:

max_value<int>
max_value<double>

In the third call, double is specified explicitly. In the final call, both arguments are converted to the same type beforehand, so T = double can once again be deduced automatically.

What comes next

So far, we have considered parameters passed by value:

template<typename T>
void foo(T value);

The deduction rules change for reference parameters:

template<typename T>
void foo(T& value);

template<typename T>
void foo(const T& value);

The differences become especially noticeable when working with const, arrays, and reference types.