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.