...

/

Function Template Argument Deduction

Function Template Argument Deduction

Develop a thorough understanding of function template argument deduction.

We have briefly talked about the fact that the compiler can sometimes deduce the template arguments from the context of the function call, allowing us to avoid explicitly specifying them. The rules for template argument deduction are more complex, and we’ll explore this topic in this lesson.

Let’s start the discussion by looking at a simple example:

Press + to interact
template<typename T>
void process(T arg)
{
std::cout << "process " << arg << ", type id: " << typeid(arg).name() << '\n';
}
int main() {
process(42); // [1] T is int
process<int>(42); // [2] T is int, redundant
process<short>(42); // [3] T is short
}

Note: The i and s in the output above represent int and short, respectively.

In this snippet, process is a function template with a single type template parameter. The calls process(42) and process<int>(42) are identical because, in the first case, the compiler is able to deduce the type of the type template parameter T as int from the value of the argument passed to the function.

Rules for deducing template arguments

When the compiler tries to deduce the template arguments, it performs the matching of the types of the template parameters with the types of the arguments used to invoke the function. There are some rules that govern this matching. The compiler can match the following:

  • Types (both cv-qualified and nonqualified) of the form T, T const, and T volatile:

Press + to interact
struct account_t
{
int number;
};
template<typename T>
void process01(T) { std::cout << "T\n"; }
template<typename T>
void process02(T const) { std::cout << "T const\n"; }
template<typename T>
void process03(T volatile) { std::cout << "T volatile\n";
}
int main() {
account_t ac{ 42 };
process01(ac); // T
process02(ac); // T const
process03(ac); // T volatile
}
  • Pointers (T*), l-value references (T&), and r-value references (T&&):

Press + to interact
template<typename T>
void process04(T*) { std::cout << "T*\n"; }
template<typename T>
void process04(T&) { std::cout << "T&\n"; }
template<typename T>
void process05(T&&) { std::cout << "T&&\n"; }
int main() {
account_t ac{ 42 };
process04(&ac); // T*
process04(ac); // T&
process05(ac); // T&&
}
  • Arrays such as T[5], or C[5][n], where C is a class type and n is a non-type template argument:

Press + to interact
template<typename T>
void process06(T[5]) { std::cout << "T[5]\n"; }
template<size_t n>
void process07(account_t[5][n])
{ std::cout << "C[5][n]\n"; }
int main() {
account_t arr1[5] {};
process06(arr1); // T[5]
account_t ac{ 42 };
process06(&ac); // T[5]
account_t arr2[5][3];
process07(arr2); // C[5][n]
}
    ...